diff --git a/client/src/main/java/client/Main.java b/client/src/main/java/client/Main.java index e722b2d..55d7fa7 100644 --- a/client/src/main/java/client/Main.java +++ b/client/src/main/java/client/Main.java @@ -17,14 +17,11 @@ package client; import static com.google.inject.Guice.createInjector; -import client.scenes.AddStepsCtrl; import client.scenes.MainCtrl; -import client.scenes.AddIngredientCtrl; -import client.scenes.AddNameCtrl; import client.scenes.FoodpalApplicationCtrl; +import client.utils.ServerUtils; import com.google.inject.Injector; -import client.utils.ServerUtilsExample; import javafx.application.Application; import javafx.stage.Stage; @@ -40,19 +37,15 @@ public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { - var serverUtils = INJECTOR.getInstance(ServerUtilsExample.class); + var serverUtils = INJECTOR.getInstance(ServerUtils.class); if (!serverUtils.isServerAvailable()) { var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down."; System.err.println(msg); return; } - - var addName = FXML.load(AddNameCtrl.class, "client", "scenes", "AddName.fxml"); var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.fxml"); - var addIngredient = FXML.load(AddIngredientCtrl.class, "client", "scenes", "AddIngredient.fxml"); - var addStep = FXML.load(AddStepsCtrl.class, "client", "scenes", "AddSteps.fxml"); var mainCtrl = INJECTOR.getInstance(MainCtrl.class); - mainCtrl.setup(primaryStage, addName, foodpal, addIngredient, addStep); + mainCtrl.setup(primaryStage, foodpal); } } \ No newline at end of file diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java index f0d95ed..4f3464e 100644 --- a/client/src/main/java/client/MyModule.java +++ b/client/src/main/java/client/MyModule.java @@ -16,12 +16,13 @@ package client; import client.scenes.FoodpalApplicationCtrl; +import client.scenes.recipe.IngredientListCtrl; +import client.scenes.recipe.RecipeStepListCtrl; import client.utils.LocaleManager; import com.google.inject.Binder; import com.google.inject.Module; import com.google.inject.Scopes; -import client.scenes.AddNameCtrl; import client.scenes.MainCtrl; public class MyModule implements Module { @@ -29,9 +30,9 @@ public class MyModule implements Module { @Override public void configure(Binder binder) { binder.bind(MainCtrl.class).in(Scopes.SINGLETON); - binder.bind(AddNameCtrl.class).in(Scopes.SINGLETON); binder.bind(FoodpalApplicationCtrl.class).in(Scopes.SINGLETON); - + binder.bind(IngredientListCtrl.class).in(Scopes.SINGLETON); + binder.bind(RecipeStepListCtrl.class).in(Scopes.SINGLETON); binder.bind(LocaleManager.class).in(Scopes.SINGLETON); } } \ No newline at end of file diff --git a/client/src/main/java/client/exception/UpdateException.java b/client/src/main/java/client/exception/UpdateException.java new file mode 100644 index 0000000..5439018 --- /dev/null +++ b/client/src/main/java/client/exception/UpdateException.java @@ -0,0 +1,7 @@ +package client.exception; + +public class UpdateException extends RuntimeException { + public UpdateException(String message) { + super(message); + } +} diff --git a/client/src/main/java/client/scenes/AddIngredientCtrl.java b/client/src/main/java/client/scenes/AddIngredientCtrl.java deleted file mode 100644 index 70d6924..0000000 --- a/client/src/main/java/client/scenes/AddIngredientCtrl.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package client.scenes; - -import client.utils.LocaleAware; -import client.utils.LocaleManager; -import client.utils.ServerUtils; -import com.google.inject.Inject; -import commons.Recipe; -import jakarta.ws.rs.WebApplicationException; -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyEvent; -import javafx.stage.Modality; - -import java.io.IOException; - -public class AddIngredientCtrl implements LocaleAware { - - private final ServerUtils server; - private final MainCtrl mainCtrl; - private final FoodpalApplicationCtrl foodpalCtrl; - - private final LocaleManager localeManager; - - @FXML - public TextField ingredient; - - @FXML - public Button okButton; - - @FXML - public Button cancelButton; - - @FXML - public Label ingredientLabel; - - @Inject - public AddIngredientCtrl(ServerUtils server, MainCtrl mainCtrl, - FoodpalApplicationCtrl foodpalCtrl, LocaleManager localeManager) { - this.mainCtrl = mainCtrl; - this.server = server; - this.foodpalCtrl = foodpalCtrl; - this.localeManager = localeManager; - } - - @Override - public void updateText() { - ingredientLabel.setText(getLocaleString("add.ingredient.label")); - okButton.setText(getLocaleString("button.ok")); - cancelButton.setText(getLocaleString("button.cancel")); - } - - @Override - public LocaleManager getLocaleManager() { - return localeManager; - } - - public void cancel() throws IOException, InterruptedException { - clearFields(); - mainCtrl.showFoodpal(); - } - - public void ok() throws IOException, InterruptedException { - try { - Recipe selected = foodpalCtrl.getSelectedRecipe(); - server.addRecipeIngredient(selected, ingredient.getText()); - } catch (WebApplicationException e) { - - var alert = new Alert(Alert.AlertType.ERROR); - alert.initModality(Modality.APPLICATION_MODAL); - alert.setContentText(e.getMessage()); - alert.showAndWait(); - return; - } - - - clearFields(); - mainCtrl.showFoodpal(); - } - - private void clearFields() { - ingredient.clear(); - } - - public void keyPressed(KeyEvent e) throws IOException, InterruptedException { - switch (e.getCode()) { - case ENTER: - ok(); - break; - case ESCAPE: - cancel(); - break; - default: - break; - } - } - -} \ No newline at end of file diff --git a/client/src/main/java/client/scenes/AddNameCtrl.java b/client/src/main/java/client/scenes/AddNameCtrl.java deleted file mode 100644 index 73f86e6..0000000 --- a/client/src/main/java/client/scenes/AddNameCtrl.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package client.scenes; - -import client.utils.LocaleAware; -import client.utils.LocaleManager; -import client.utils.ServerUtils; -import com.google.inject.Inject; - -import jakarta.ws.rs.WebApplicationException; -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyEvent; -import javafx.stage.Modality; - -import java.io.IOException; - -public class AddNameCtrl implements LocaleAware { - - private final ServerUtils server; - private final MainCtrl mainCtrl; - - private final LocaleManager localeManager; - - @FXML - public TextField recipeName; - - @FXML - public Label recipeNameLabel; - - @FXML - public Button cancelButton; - - @FXML - public Button okButton; - - @Inject - public AddNameCtrl(ServerUtils server, MainCtrl mainCtrl, LocaleManager localeManager) { - this.mainCtrl = mainCtrl; - this.server = server; - this.localeManager = localeManager; - } - - @Override - public void updateText() { - recipeNameLabel.setText(getLocaleString("add.recipe.label")); - okButton.setText(getLocaleString("button.ok")); - cancelButton.setText(getLocaleString("button.cancel")); - } - - @Override - public LocaleManager getLocaleManager() { - return localeManager; - } - - public void cancel() throws IOException, InterruptedException { - clearFields(); - mainCtrl.showFoodpal(); - } - - public void ok() throws IOException, InterruptedException { - try { - server.addRecipeName(recipeName.getText()); - } catch (WebApplicationException e) { - - var alert = new Alert(Alert.AlertType.ERROR); - alert.initModality(Modality.APPLICATION_MODAL); - alert.setContentText(e.getMessage()); - alert.showAndWait(); - return; - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - - clearFields(); - mainCtrl.showFoodpal(); - } - - private void clearFields() { - recipeName.clear(); - } - - public void keyPressed(KeyEvent e) throws IOException, InterruptedException { - switch (e.getCode()) { - case ENTER: - ok(); - break; - case ESCAPE: - cancel(); - break; - default: - break; - } - } - -} \ No newline at end of file diff --git a/client/src/main/java/client/scenes/AddStepsCtrl.java b/client/src/main/java/client/scenes/AddStepsCtrl.java deleted file mode 100644 index abd9f51..0000000 --- a/client/src/main/java/client/scenes/AddStepsCtrl.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2021 Delft University of Technology - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package client.scenes; - -import client.utils.LocaleAware; -import client.utils.LocaleManager; -import client.utils.ServerUtils; -import com.google.inject.Inject; -import commons.Recipe; -import jakarta.ws.rs.WebApplicationException; -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyEvent; -import javafx.stage.Modality; - -import java.io.IOException; - -public class AddStepsCtrl implements LocaleAware { - - private final ServerUtils server; - private final MainCtrl mainCtrl; - private final FoodpalApplicationCtrl foodpalCtrl; - private final LocaleManager localeManager; - - @FXML - public TextField preparationStep; - - @FXML - public Button okButton; - - @FXML - public Button cancelButton; - - @FXML - public Label preparationStepLabel; - - @Inject - public AddStepsCtrl(ServerUtils server, MainCtrl mainCtrl, - FoodpalApplicationCtrl foodpalCtrl, LocaleManager localeManager) { - this.mainCtrl = mainCtrl; - this.server = server; - this.foodpalCtrl = foodpalCtrl; - this.localeManager = localeManager; - } - - @Override - public void updateText() { - preparationStepLabel.setText(getLocaleString("add.step.label")); - okButton.setText(getLocaleString("button.ok")); - cancelButton.setText(getLocaleString("button.cancel")); - } - - @Override - public LocaleManager getLocaleManager() { - return localeManager; - } - - public void cancel() throws IOException, InterruptedException { - clearFields(); - mainCtrl.showFoodpal(); - } - - public void ok() throws IOException, InterruptedException { - try { - Recipe selected = foodpalCtrl.getSelectedRecipe(); - server.addRecipeStep(selected, preparationStep.getText()); - } catch (WebApplicationException e) { - - var alert = new Alert(Alert.AlertType.ERROR); - alert.initModality(Modality.APPLICATION_MODAL); - alert.setContentText(e.getMessage()); - alert.showAndWait(); - return; - } - - - clearFields(); - mainCtrl.showFoodpal(); - } - - private void clearFields() { - preparationStep.clear(); - } - - public void keyPressed(KeyEvent e) throws IOException, InterruptedException { - switch (e.getCode()) { - case ENTER: - ok(); - break; - case ESCAPE: - cancel(); - break; - default: - break; - } - } - -} \ No newline at end of file diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 9257bfc..ed97139 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -6,24 +6,41 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import client.exception.UpdateException; +import client.scenes.recipe.IngredientListCtrl; +import client.scenes.recipe.RecipeStepListCtrl; +import client.utils.DefaultRecipeFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; import client.utils.ServerUtils; + import commons.Recipe; import jakarta.inject.Inject; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; public class FoodpalApplicationCtrl implements LocaleAware { - private final MainCtrl mainCtrl; private final ServerUtils server; private final LocaleManager localeManager; + private final IngredientListCtrl ingredientListCtrl; + private final RecipeStepListCtrl stepListCtrl; + public VBox detailsScreen; + public HBox editableTitleArea; + + + // all of these aren't used with only my part of the code // everything in the top bar === @FXML private Button flagEnButton; //already here for advanced stuff @@ -51,8 +68,6 @@ public class FoodpalApplicationCtrl implements LocaleAware { public Button cloneRecipeButton; // === CENTER: RECIPE DETAILS === - @FXML - private Label recipeNameLabel; @FXML private Button editRecipeTitleButton; @@ -63,33 +78,50 @@ public class FoodpalApplicationCtrl implements LocaleAware { @FXML public Button printRecipeButton; - @FXML - public Label ingredientsLabel; - - @FXML - public Label preparationLabel; - - @FXML - private ListView ingredientsListView; - - @FXML - private Button addIngredientButton; - - @FXML - private ListView preparationListView; - - @FXML - private Button addPreparationStepButton; - @Inject - public FoodpalApplicationCtrl(MainCtrl mainCtrl, ServerUtils server, LocaleManager localeManager) { - this.mainCtrl = mainCtrl; + public FoodpalApplicationCtrl( + ServerUtils server, + LocaleManager localeManager, + IngredientListCtrl ingredientListCtrl, + RecipeStepListCtrl stepListCtrl + ) { this.server = server; this.localeManager = localeManager; + this.ingredientListCtrl = ingredientListCtrl; + this.stepListCtrl = stepListCtrl; } - @Override public void initializeComponents() { + // TODO Reduce code duplication?? + // Initialize callback for ingredient list updates + this.ingredientListCtrl.setUpdateCallback(newList -> { + Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem(); + if (selectedRecipe == null) { + // edge case error for NPE. + throw new NullPointerException("Null recipe whereas ingredients are edited"); + } + selectedRecipe.setIngredients(newList); + try { + // propagate changes to server + server.updateRecipe(selectedRecipe); + } catch (IOException | InterruptedException e) { + throw new UpdateException("Unable to update recipe to server for " + selectedRecipe); + } + }); + this.stepListCtrl.setUpdateCallback(newList -> { + Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem(); + if (selectedRecipe == null) { + // edge case error for NPE. + throw new NullPointerException("Null recipe whereas ingredients are edited"); + } + selectedRecipe.setPreparationSteps(newList); + try { + // propagate changes to server + server.updateRecipe(selectedRecipe); + } catch (IOException | InterruptedException e) { + throw new UpdateException("Unable to update recipe to server for " + selectedRecipe); + } + }); // Show recipe name in the list recipeList.setCellFactory(list -> new ListCell<>() { @Override @@ -114,7 +146,13 @@ public class FoodpalApplicationCtrl implements LocaleAware { refresh(); } - + private void showName(String name) { + final int NAME_FONT_SIZE = 20; + editableTitleArea.getChildren().clear(); + Label nameLabel = new Label(name); + nameLabel.setFont(new Font("System Bold", NAME_FONT_SIZE)); + editableTitleArea.getChildren().add(nameLabel); + } @Override public void updateText() { addRecipeButton.setText(getLocaleString("menu.button.add.recipe")); @@ -126,11 +164,13 @@ public class FoodpalApplicationCtrl implements LocaleAware { printRecipeButton.setText(getLocaleString("menu.button.print")); recipesLabel.setText(getLocaleString("menu.label.recipes")); - ingredientsLabel.setText(getLocaleString("menu.label.ingredients")); - preparationLabel.setText(getLocaleString("menu.label.preparation")); - addIngredientButton.setText(getLocaleString("menu.button.add.ingredient")); - addPreparationStepButton.setText(getLocaleString("menu.button.add.step")); + // TODO propagate ResourceBundle lang changes to nested controller + // ingredientsLabel.setText(getLocaleString("menu.label.ingredients")); + // preparationLabel.setText(getLocaleString("menu.label.preparation")); + + // addIngredientButton.setText(getLocaleString("menu.button.add.ingredient")); + // addPreparationStepButton.setText(getLocaleString("menu.button.add.step")); } @Override @@ -138,25 +178,18 @@ public class FoodpalApplicationCtrl implements LocaleAware { return localeManager; } - // till the all the code from everyone is implemented for now to not have errors private void showRecipeDetails(Recipe recipe) { if (recipe == null) { - recipeNameLabel.setText(""); - ingredientsListView.getItems().clear(); - preparationListView.getItems().clear(); return; } - - recipeNameLabel.setText(recipe.getName()); - ingredientsListView.getItems().setAll(recipe.getIngredients()); - preparationListView.getItems().setAll(recipe.getPreparationSteps()); + showName(recipe.getName()); + ingredientListCtrl.refetchFromRecipe(recipe); + stepListCtrl.refetchFromRecipe(recipe); } // Button handlers @FXML public void refresh() { - // TODO: someone else doing this - List recipes; try { recipes = server.getRecipes(); @@ -171,15 +204,38 @@ public class FoodpalApplicationCtrl implements LocaleAware { if (!recipes.isEmpty()) { recipeList.getSelectionModel().selectFirst(); } + detailsScreen.visibleProperty().set(!recipes.isEmpty()); + } + private void printError(String msg) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setContentText(msg); + alert.showAndWait(); } - /** - * Adds a recipe, by going to a different scene, there you insert the title and bam recipe created + * Adds a recipe, by providing a default name "Untitled recipe (n)" + * The UX flow is now: + *
    + *
  1. User creates an untitled recipe
  2. + *
  3. The application autofocuses on the edit box (not exactly automagically yet)
  4. + *
  5. User edits the name to whatever they want
  6. + *
  7. Profit??
  8. + *
*/ @FXML private void addRecipe() { - // Navigate to "create recipe" screen (should be like a pop-up or new screen or just another place in the app) - mainCtrl.showAddName(); + // a default factory provides the value + Recipe newRecipe = DefaultRecipeFactory.getDefaultRecipe(); + try { + server.addRecipe(newRecipe); + refresh(); + // the list focuses on the new recipe + // otherwise strange issues occur when the autofocus on edit box is called + recipeList.getFocusModel().focus(recipeList.getItems().indexOf(newRecipe)); + } catch (IOException | InterruptedException e) { + printError("Error occurred when adding recipe!"); + } + // Calls edit after the recipe has been created, seamless name editing + editRecipeTitle(); } @FXML @@ -191,7 +247,10 @@ public class FoodpalApplicationCtrl implements LocaleAware { server.deleteRecipe(selected.getId()); - recipeList.getItems().remove(selected); + // prefer a global refresh (or WS-based update upon its impl) + // rather than recipeList.getItems().remove(selected); + // to maintain single source of truth from the server. + refresh(); } @FXML @@ -200,39 +259,42 @@ public class FoodpalApplicationCtrl implements LocaleAware { if (selected == null) { return; } - // Let MainCtrl open the full detail screen - mainCtrl.showAddName(); //I had showrecipedetail but intelij says showoverview + detailsScreen.visibleProperty().set(true); } + /** + * Revised edit recipe control flow, deprecates the use of a separate AddNameCtrl + * This is automagically called when a new recipe is created, making for a more seamless UX + */ @FXML private void editRecipeTitle() { - // TODO: someone else todo - // For now reuse openSelectedRecipe() - openSelectedRecipe(); + editableTitleArea.getChildren().clear(); + TextField edit = new TextField(); + edit.setOnKeyPressed(event -> { + if (event.getCode() != KeyCode.ENTER) { + return; + } + String newName = edit.getText(); + Recipe selected = recipeList.getSelectionModel().getSelectedItem(); + if (selected == null) { + // edge case, prob won't happen but best to handle it later + throw new NullPointerException("No recipe selected while name was changed!"); + } + selected.setName(newName); + try { + server.updateRecipe(selected); + refresh(); + recipeList.getSelectionModel().select(selected); + } catch (IOException | InterruptedException e) { + // throw a nice blanket UpdateException + throw new UpdateException("Error occurred when updating recipe name!"); + } + showName(edit.getText()); + }); + editableTitleArea.getChildren().add(edit); + edit.requestFocus(); } - /** - * You can add ingredients. you get send to a different scene that creates the ingredient - * It doesn't automatically refresh yet so before you can see the new ingredient you have to first click on a - * different recipe and come back after - */ - @FXML - private void addIngredient() { - System.out.println("Add ingredient clicked"); - mainCtrl.showAddIngredient(); - } - - /** - * You can add steps. you get send to a different scene that creates the Step - * It doesn't automatically refresh yet so before you can see the new step you have to first click on a - * different recipe and come back after - */ - @FXML - private void addPreparationStep() { - System.out.println("Add preparation step clicked"); - mainCtrl.showAddSteps(); - - } // Language buttons @FXML @@ -247,14 +309,6 @@ public class FoodpalApplicationCtrl implements LocaleAware { System.out.println("Recipe printed"); } - /** - * Get the recipe that was selected - * @return the selected recipe - */ - public Recipe getSelectedRecipe() { - return recipeList.getSelectionModel().getSelectedItem(); - } - /** * Clones a recipe, when clicking on the button "clone" */ @@ -264,9 +318,9 @@ public class FoodpalApplicationCtrl implements LocaleAware { return; } - server.cloneRecipe(selected.getId()); + Recipe cloned = server.cloneRecipe(selected.getId()); refresh(); - + recipeList.getSelectionModel().select(cloned); } } diff --git a/client/src/main/java/client/scenes/MainCtrl.java b/client/src/main/java/client/scenes/MainCtrl.java index 0ad2f49..6499420 100644 --- a/client/src/main/java/client/scenes/MainCtrl.java +++ b/client/src/main/java/client/scenes/MainCtrl.java @@ -15,9 +15,6 @@ */ package client.scenes; -import client.utils.LocaleAware; -import client.utils.LocaleManager; -import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; import javafx.scene.Scene; @@ -26,116 +23,28 @@ import javafx.util.Pair; import java.io.IOException; -public class MainCtrl implements LocaleAware { - private final LocaleManager localeManager; - +public class MainCtrl { @FXML private Stage primaryStage; - @FXML - private AddNameCtrl addNameCtrl; - private Scene addName; - @FXML private FoodpalApplicationCtrl foodpalCtrl; private Scene foodpal; - @FXML - private AddIngredientCtrl addIngredientCtrl; - private Scene addIngredient; - - @FXML - private AddStepsCtrl addStepsCtrl; - private Scene addStep; - - private String addNameTitle = "add.name.title"; - private String addIngredientTitle = "add.step.title"; - private String addStepTitle = "add.ingredient.title"; - - @Inject - public MainCtrl(LocaleManager localeManager) { - this.localeManager = localeManager; - } - public void setup(Stage primaryStage, - Pair addName, - Pair foodpal, - Pair addIngredient, - Pair addStep) throws IOException, InterruptedException { + Pair foodpal) throws IOException, InterruptedException { this.primaryStage = primaryStage; - this.addNameCtrl = addName.getKey(); - this.addName = new Scene(addName.getValue()); - this.foodpalCtrl = foodpal.getKey(); this.foodpal = new Scene(foodpal.getValue()); - this.addIngredientCtrl = addIngredient.getKey(); - this.addIngredient = new Scene(addIngredient.getValue()); - - this.addStepsCtrl = addStep.getKey(); - this.addStep = new Scene(addStep.getValue()); - showFoodpal(); primaryStage.show(); - - initialize(); // Initialize LocaleManager stuff manually since MainCtrl isn't loaded from FXML. } - - @Override - public void updateText() { - addNameTitle = getLocaleString("add.recipe.title"); - addStepTitle = getLocaleString("add.step.title"); - addIngredientTitle = getLocaleString("add.ingredient.title"); - } - - @Override - public LocaleManager getLocaleManager() { - return localeManager; - } - - public void showAddName() { - primaryStage.setTitle(addNameTitle); - primaryStage.setScene(addName); - addName.setOnKeyPressed(e -> { - try { - addNameCtrl.keyPressed(e); - } catch (IOException | InterruptedException ex) { - throw new RuntimeException(ex); - } - }); - } - public void showFoodpal() { primaryStage.setTitle("FoodPal"); primaryStage.setScene(foodpal); foodpalCtrl.refresh(); } - - public void showAddIngredient() { - primaryStage.setTitle(addIngredientTitle); - primaryStage.setScene(addIngredient); - addIngredient.setOnKeyPressed(e -> { - try { - addIngredientCtrl.keyPressed(e); - } catch (IOException | InterruptedException ex) { - throw new RuntimeException(ex); - } - }); - - } - - public void showAddSteps() { - primaryStage.setTitle(addStepTitle); - primaryStage.setScene(addStep); - addStep.setOnKeyPressed(e -> { - try { - addStepsCtrl.keyPressed(e); - } catch (IOException | InterruptedException ex) { - throw new RuntimeException(ex); - } - }); - - } } \ No newline at end of file diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCell.java b/client/src/main/java/client/scenes/recipe/IngredientListCell.java index ac317d8..f420bff 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCell.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCell.java @@ -1,78 +1,18 @@ package client.scenes.recipe; -import javafx.scene.control.ListCell; import javafx.scene.control.TextField; -import javafx.scene.input.KeyCode; /** - * A custom ListCell for displaying and editing ingredients in an + * A custom {@link OrderedEditableListCell} for displaying and editing ingredients in an * IngredientList. Allows inline editing of ingredient names. * * @see IngredientListCtrl */ -public class IngredientListCell extends ListCell { +public class IngredientListCell extends OrderedEditableListCell { // TODO: change all this to use actual Ingredient objects // and all that :) - @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - - if (empty || item == null) { - this.setText(null); - } else { - this.setText(item); - } - } - - @Override - public void startEdit() { - super.startEdit(); - - TextField textField = new TextField(this.getItem()); - - // Commit edit on Enter key press - textField.setOnAction(event -> { - this.commitEdit(textField.getText()); - }); - - // Cancel edit on Escape key press - textField.setOnKeyReleased(event -> { - if (event.getCode() == KeyCode.ESCAPE) { - this.cancelEdit(); - } - }); - - this.setText(null); - this.setGraphic(textField); - } - - /** - * Check if the input is valid (non-empty). - * - * @param input The input string to validate. - * @return true if valid, false otherwise. - */ - private boolean isInputValid(String input) { - return input != null && !input.trim().isEmpty(); - } - - @Override - public void cancelEdit() { - super.cancelEdit(); - - this.setText(this.getItem()); - this.setGraphic(null); - } - - @Override - public void commitEdit(String newValue) { - this.setGraphic(null); - - if (!isInputValid(newValue)) { - newValue = this.getItem(); // Revert to old value if input is invalid - } - - super.commitEdit(newValue); + protected String fromInput(TextField inputField) { + return inputField.getText(); } } diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java index 74193e6..859c28a 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java @@ -1,17 +1,18 @@ package client.scenes.recipe; +import client.utils.LocaleAware; +import client.utils.LocaleManager; +import com.google.inject.Inject; import commons.Recipe; -import java.net.URL; import java.util.ArrayList; import java.util.List; -import java.util.ResourceBundle; -import java.util.function.Function; +import java.util.function.Consumer; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.ListView.EditEvent; @@ -19,9 +20,15 @@ import javafx.scene.control.ListView.EditEvent; * Controller for the ingredient list view in the recipe detail scene. * Manages displaying, adding, editing, and deleting ingredients. * - * @see RecipeStepListCell The custom cell implementation. + * @see IngredientListCell The custom cell implementation. */ -public class IngredientListCtrl implements Initializable { +public class IngredientListCtrl implements LocaleAware { + private final LocaleManager localeManager; + @Inject + public IngredientListCtrl(LocaleManager localeManager) { + this.localeManager = localeManager; + } + /** *

* The list of ingredients currently displayed. @@ -29,7 +36,7 @@ public class IngredientListCtrl implements Initializable { * As the ingredient list in {@link Recipe} is immutable, * this copies that list upon initialization to this mutable list. *
- * Please use the {@link #setUpdateCallback(Function)} function to listen for + * Please use the {@link #setUpdateCallback(Consumer)} function to listen for * changes and update the recipe accordingly. *

*/ @@ -38,15 +45,18 @@ public class IngredientListCtrl implements Initializable { /** * A callback function that is called when the ingredient list is updated. */ - private Function, Void> updateCallback; + private Consumer> updateCallback; @FXML - ListView ingredientListView; + public ListView ingredientListView; @FXML - Button addIngredientButton; + public Label ingredientsLabel; + @FXML - Button deleteIngredientButton; + public Button addIngredientButton; + @FXML + public Button deleteIngredientButton; /** * Set the recipe and refetch data from it. @@ -72,7 +82,7 @@ public class IngredientListCtrl implements Initializable { * * @param callback The function to call upon each update. */ - public void setUpdateCallback(Function, Void> callback) { + public void setUpdateCallback(Consumer> callback) { this.updateCallback = callback; } @@ -91,7 +101,7 @@ public class IngredientListCtrl implements Initializable { private void handleIngredientAdd(ActionEvent event) { this.ingredients.add("Ingredient " + (this.ingredients.size() + 1)); this.refresh(); - this.updateCallback.apply(this.ingredients); + this.updateCallback.accept(this.ingredients); var select = this.ingredientListView.getSelectionModel(); select.select(this.ingredients.size() - 1); @@ -108,7 +118,7 @@ public class IngredientListCtrl implements Initializable { this.ingredients.set(index, newValue); this.refresh(); - this.updateCallback.apply(this.ingredients); + this.updateCallback.accept(this.ingredients); } /** @@ -126,19 +136,31 @@ public class IngredientListCtrl implements Initializable { this.ingredients.remove(selectedIndex); this.refresh(); - this.updateCallback.apply(this.ingredients); + this.updateCallback.accept(this.ingredients); } - @FXML - public void initialize(URL location, ResourceBundle resources) { + @Override + public void updateText() { + System.out.println("updatetext called on ingredientListCtrl"); + ingredientsLabel.setText(getLocaleString("menu.label.ingredients")); + addIngredientButton.setText(getLocaleString("menu.button.add.ingredient")); + deleteIngredientButton.setText(getLocaleString("menu.button.remove.ingredient")); + } + + @Override + public LocaleManager getLocaleManager() { + return localeManager; + } + + @Override + public void initializeComponents() { // TODO: set up communication with the server // this would probably be best done with the callback (so this class doesn't // interact with the server at all) - this.ingredientListView.setEditable(true); this.ingredientListView.setCellFactory( list -> { - return new RecipeStepListCell(); + return new IngredientListCell(); }); this.ingredientListView.setOnEditCommit(this::handleIngredientEdit); diff --git a/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java b/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java new file mode 100644 index 0000000..8716c3a --- /dev/null +++ b/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java @@ -0,0 +1,78 @@ +package client.scenes.recipe; + +import javafx.scene.control.ListCell; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; + +public abstract class OrderedEditableListCell extends ListCell { + /** + * Get the display text for the given item, prefixed with its index. + * Looks like "1. description". + * + * @param item The description. + * @return The display text. + */ + private String getDisplayText(String item) { + return (this.getIndex() + 1) + ". " + item; + } + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + this.setText(null); + } else { + this.setText(this.getDisplayText(item.toString())); + } + } + public void startEdit() { + super.startEdit(); + + TextField textField = new TextField(this.getItem().toString()); + + // Commit edit on Enter key press + textField.setOnAction(event -> { + this.commitEdit(fromInput(textField)); + }); + + // Cancel edit on Escape key press + textField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ESCAPE) { + this.cancelEdit(); + } + }); + + this.setText(null); + this.setGraphic(textField); + } + + protected abstract T fromInput(TextField inputField); + /** + * Check if the input is valid (non-empty). + * + * @param input The input string to validate. + * @return true if valid, false otherwise. + */ + private boolean isInputValid(String input) { + return input != null && !input.trim().isEmpty(); + } + + @Override + public void cancelEdit() { + super.cancelEdit(); + + this.setText(this.getItem().toString()); + this.setGraphic(null); + } + + @Override + public void commitEdit(T newValue) { + this.setGraphic(null); + + if (!isInputValid(newValue.toString())) { + newValue = this.getItem(); // Revert to old value if input is invalid + } + + super.commitEdit(newValue); + } +} diff --git a/client/src/main/java/client/scenes/recipe/RecipeStepListCell.java b/client/src/main/java/client/scenes/recipe/RecipeStepListCell.java index 6d6e992..bf57c13 100644 --- a/client/src/main/java/client/scenes/recipe/RecipeStepListCell.java +++ b/client/src/main/java/client/scenes/recipe/RecipeStepListCell.java @@ -1,96 +1,15 @@ package client.scenes.recipe; -import javafx.scene.control.ListCell; import javafx.scene.control.TextField; -import javafx.scene.input.KeyCode; - /** * A custom ListCell for displaying and editing ingredients in an * RecipeStepList. Allows inline editing of ingredient names. * * @see RecipeStepListCtrl */ -public class RecipeStepListCell extends ListCell { - /** - * Get the display text for the given item, prefixed with its index. - * Looks like "1. Step description". - * - * @param item The step description. - * @return The display text. - */ - private String getDisplayText(String item) { - return this.getIndex() + ". " + item; - } - - /** - * Get the display text for the current item. - * Looks like "1. Step description". - * - * @return The display text. - */ - private String getDisplayText() { - return this.getDisplayText(this.getItem()); - } - +public class RecipeStepListCell extends OrderedEditableListCell { @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - - if (empty || item == null) { - this.setText(null); - } else { - this.setText(this.getDisplayText(item)); - } - } - - @Override - public void startEdit() { - super.startEdit(); - - TextField textField = new TextField(this.getItem()); - - // Commit edit on Enter key press - textField.setOnAction(event -> { - this.commitEdit(textField.getText()); - }); - - // Cancel edit on Escape key press - textField.setOnKeyReleased(event -> { - if (event.getCode() == KeyCode.ESCAPE) { - this.cancelEdit(); - } - }); - - this.setText(null); - this.setGraphic(textField); - } - - /** - * Check if the input is valid (non-empty). - * - * @param input The input string to validate. - * @return true if valid, false otherwise. - */ - private boolean isInputValid(String input) { - return input != null && !input.trim().isEmpty(); - } - - @Override - public void cancelEdit() { - super.cancelEdit(); - - this.setText(this.getDisplayText()); - this.setGraphic(null); - } - - @Override - public void commitEdit(String newValue) { - this.setGraphic(null); - - if (!isInputValid(newValue)) { - newValue = this.getItem(); // Revert to old value if input is invalid - } - - super.commitEdit(newValue); + protected String fromInput(TextField inputField) { + return inputField.getText(); } } diff --git a/client/src/main/java/client/scenes/recipe/RecipeStepListCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeStepListCtrl.java index 7ed9c36..8bfcce4 100644 --- a/client/src/main/java/client/scenes/recipe/RecipeStepListCtrl.java +++ b/client/src/main/java/client/scenes/recipe/RecipeStepListCtrl.java @@ -1,17 +1,18 @@ package client.scenes.recipe; +import client.utils.LocaleAware; +import client.utils.LocaleManager; +import com.google.inject.Inject; import commons.Recipe; -import java.net.URL; import java.util.ArrayList; import java.util.List; -import java.util.ResourceBundle; -import java.util.function.Function; +import java.util.function.Consumer; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.ListView.EditEvent; @@ -21,7 +22,16 @@ import javafx.scene.control.ListView.EditEvent; * * @see RecipeStepListCell The custom cell implementation. */ -public class RecipeStepListCtrl implements Initializable { +public class RecipeStepListCtrl implements LocaleAware { + private final LocaleManager localeManager; + + @FXML + public Label stepsLabel; + + @Inject + public RecipeStepListCtrl(LocaleManager localeManager) { + this.localeManager = localeManager; + } /** *

* The list of recipe steps currently displayed. @@ -29,7 +39,7 @@ public class RecipeStepListCtrl implements Initializable { * As the step list in {@link Recipe} is immutable, * this copies that list upon initialization to this mutable list. *
- * Please use the {@link #setUpdateCallback(Function)} function to listen for + * Please use the {@link #setUpdateCallback(Consumer)} function to listen for * changes and update the recipe accordingly. *

*/ @@ -38,7 +48,7 @@ public class RecipeStepListCtrl implements Initializable { /** * A callback function that is called when the step list is updated. */ - private Function, Void> updateCallback; + private Consumer> updateCallback; @FXML ListView recipeStepListView; @@ -72,7 +82,7 @@ public class RecipeStepListCtrl implements Initializable { * * @param callback The function to call upon each update. */ - public void setUpdateCallback(Function, Void> callback) { + public void setUpdateCallback(Consumer> callback) { this.updateCallback = callback; } @@ -91,7 +101,7 @@ public class RecipeStepListCtrl implements Initializable { private void handleIngredientAdd(ActionEvent event) { this.steps.add("Step " + (this.steps.size() + 1)); this.refresh(); - this.updateCallback.apply(this.steps); + this.updateCallback.accept(this.steps); var select = this.recipeStepListView.getSelectionModel(); select.select(this.steps.size() - 1); @@ -108,7 +118,7 @@ public class RecipeStepListCtrl implements Initializable { this.steps.set(index, newValue); this.refresh(); - this.updateCallback.apply(this.steps); + this.updateCallback.accept(this.steps); } /** @@ -126,21 +136,28 @@ public class RecipeStepListCtrl implements Initializable { this.steps.remove(selectedIndex); this.refresh(); - this.updateCallback.apply(this.steps); + this.updateCallback.accept(this.steps); } - @FXML - public void initialize(URL location, ResourceBundle resources) { - // TODO: set up communication with the server - // this would probably be best done with the callback (so this class doesn't - // interact with the server at all) + @Override + public void updateText() { + stepsLabel.setText(getLocaleString("menu.label.preparation")); + addStepButton.setText(getLocaleString("menu.button.add.step")); + deleteStepButton.setText(getLocaleString("menu.button.remove.step")); + } + @Override + public LocaleManager getLocaleManager() { + return localeManager; + } + + @Override + public void initializeComponents() { this.recipeStepListView.setEditable(true); this.recipeStepListView.setCellFactory( list -> { return new RecipeStepListCell(); }); - this.recipeStepListView.setOnEditCommit(this::handleIngredientEdit); this.addStepButton.setOnAction(this::handleIngredientAdd); this.deleteStepButton.setOnAction(this::handleIngredientDelete); diff --git a/client/src/main/java/client/utils/DefaultRecipeFactory.java b/client/src/main/java/client/utils/DefaultRecipeFactory.java new file mode 100644 index 0000000..0a94786 --- /dev/null +++ b/client/src/main/java/client/utils/DefaultRecipeFactory.java @@ -0,0 +1,15 @@ +package client.utils; + +import commons.Recipe; + +import java.util.List; + +public class DefaultRecipeFactory { + public static Recipe getDefaultRecipe() { + return new Recipe( + null, + "Untitled recipe", + List.of(), + List.of()); + } +} diff --git a/client/src/main/resources/client/scenes/AddIngredient.fxml b/client/src/main/resources/client/scenes/AddIngredient.fxml deleted file mode 100644 index 5aaddde..0000000 --- a/client/src/main/resources/client/scenes/AddIngredient.fxml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - -