Merge branch 'feature/revised-client-ux-flow' into 'main'

feat: Revised UX flow for recipe details editing

Closes #13

See merge request cse1105/2025-2026/teams/csep-team-76!13
This commit is contained in:
Zhongheng Liu 2025-12-05 12:56:06 +01:00
commit 6129fc2a5a
21 changed files with 335 additions and 794 deletions

View file

@ -17,14 +17,11 @@ package client;
import static com.google.inject.Guice.createInjector; import static com.google.inject.Guice.createInjector;
import client.scenes.AddStepsCtrl;
import client.scenes.MainCtrl; import client.scenes.MainCtrl;
import client.scenes.AddIngredientCtrl;
import client.scenes.AddNameCtrl;
import client.scenes.FoodpalApplicationCtrl; import client.scenes.FoodpalApplicationCtrl;
import client.utils.ServerUtils;
import com.google.inject.Injector; import com.google.inject.Injector;
import client.utils.ServerUtilsExample;
import javafx.application.Application; import javafx.application.Application;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -40,19 +37,15 @@ public class Main extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
var serverUtils = INJECTOR.getInstance(ServerUtilsExample.class); var serverUtils = INJECTOR.getInstance(ServerUtils.class);
if (!serverUtils.isServerAvailable()) { if (!serverUtils.isServerAvailable()) {
var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down."; var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down.";
System.err.println(msg); System.err.println(msg);
return; return;
} }
var addName = FXML.load(AddNameCtrl.class, "client", "scenes", "AddName.fxml");
var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.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); var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
mainCtrl.setup(primaryStage, addName, foodpal, addIngredient, addStep); mainCtrl.setup(primaryStage, foodpal);
} }
} }

View file

@ -16,12 +16,13 @@
package client; package client;
import client.scenes.FoodpalApplicationCtrl; import client.scenes.FoodpalApplicationCtrl;
import client.scenes.recipe.IngredientListCtrl;
import client.scenes.recipe.RecipeStepListCtrl;
import client.utils.LocaleManager; import client.utils.LocaleManager;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import client.scenes.AddNameCtrl;
import client.scenes.MainCtrl; import client.scenes.MainCtrl;
public class MyModule implements Module { public class MyModule implements Module {
@ -29,9 +30,9 @@ public class MyModule implements Module {
@Override @Override
public void configure(Binder binder) { public void configure(Binder binder) {
binder.bind(MainCtrl.class).in(Scopes.SINGLETON); binder.bind(MainCtrl.class).in(Scopes.SINGLETON);
binder.bind(AddNameCtrl.class).in(Scopes.SINGLETON);
binder.bind(FoodpalApplicationCtrl.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); binder.bind(LocaleManager.class).in(Scopes.SINGLETON);
} }
} }

View file

@ -0,0 +1,7 @@
package client.exception;
public class UpdateException extends RuntimeException {
public UpdateException(String message) {
super(message);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -6,24 +6,41 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; 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.LocaleAware;
import client.utils.LocaleManager; import client.utils.LocaleManager;
import client.utils.ServerUtils; import client.utils.ServerUtils;
import commons.Recipe; import commons.Recipe;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ListCell; import javafx.scene.control.ListCell;
import javafx.scene.control.ListView; 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 { public class FoodpalApplicationCtrl implements LocaleAware {
private final MainCtrl mainCtrl;
private final ServerUtils server; private final ServerUtils server;
private final LocaleManager localeManager; 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 === // everything in the top bar ===
@FXML @FXML
private Button flagEnButton; //already here for advanced stuff private Button flagEnButton; //already here for advanced stuff
@ -51,8 +68,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
public Button cloneRecipeButton; public Button cloneRecipeButton;
// === CENTER: RECIPE DETAILS === // === CENTER: RECIPE DETAILS ===
@FXML
private Label recipeNameLabel;
@FXML @FXML
private Button editRecipeTitleButton; private Button editRecipeTitleButton;
@ -63,33 +78,50 @@ public class FoodpalApplicationCtrl implements LocaleAware {
@FXML @FXML
public Button printRecipeButton; public Button printRecipeButton;
@FXML
public Label ingredientsLabel;
@FXML
public Label preparationLabel;
@FXML
private ListView<String> ingredientsListView;
@FXML
private Button addIngredientButton;
@FXML
private ListView<String> preparationListView;
@FXML
private Button addPreparationStepButton;
@Inject @Inject
public FoodpalApplicationCtrl(MainCtrl mainCtrl, ServerUtils server, LocaleManager localeManager) { public FoodpalApplicationCtrl(
this.mainCtrl = mainCtrl; ServerUtils server,
LocaleManager localeManager,
IngredientListCtrl ingredientListCtrl,
RecipeStepListCtrl stepListCtrl
) {
this.server = server; this.server = server;
this.localeManager = localeManager; this.localeManager = localeManager;
this.ingredientListCtrl = ingredientListCtrl;
this.stepListCtrl = stepListCtrl;
} }
@Override @Override
public void initializeComponents() { 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 // Show recipe name in the list
recipeList.setCellFactory(list -> new ListCell<>() { recipeList.setCellFactory(list -> new ListCell<>() {
@Override @Override
@ -114,7 +146,13 @@ public class FoodpalApplicationCtrl implements LocaleAware {
refresh(); 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 @Override
public void updateText() { public void updateText() {
addRecipeButton.setText(getLocaleString("menu.button.add.recipe")); addRecipeButton.setText(getLocaleString("menu.button.add.recipe"));
@ -126,11 +164,13 @@ public class FoodpalApplicationCtrl implements LocaleAware {
printRecipeButton.setText(getLocaleString("menu.button.print")); printRecipeButton.setText(getLocaleString("menu.button.print"));
recipesLabel.setText(getLocaleString("menu.label.recipes")); 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")); // TODO propagate ResourceBundle lang changes to nested controller
addPreparationStepButton.setText(getLocaleString("menu.button.add.step")); // 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 @Override
@ -138,25 +178,18 @@ public class FoodpalApplicationCtrl implements LocaleAware {
return localeManager; return localeManager;
} }
// till the all the code from everyone is implemented for now to not have errors
private void showRecipeDetails(Recipe recipe) { private void showRecipeDetails(Recipe recipe) {
if (recipe == null) { if (recipe == null) {
recipeNameLabel.setText("");
ingredientsListView.getItems().clear();
preparationListView.getItems().clear();
return; return;
} }
showName(recipe.getName());
recipeNameLabel.setText(recipe.getName()); ingredientListCtrl.refetchFromRecipe(recipe);
ingredientsListView.getItems().setAll(recipe.getIngredients()); stepListCtrl.refetchFromRecipe(recipe);
preparationListView.getItems().setAll(recipe.getPreparationSteps());
} }
// Button handlers // Button handlers
@FXML @FXML
public void refresh() { public void refresh() {
// TODO: someone else doing this
List<Recipe> recipes; List<Recipe> recipes;
try { try {
recipes = server.getRecipes(); recipes = server.getRecipes();
@ -171,15 +204,38 @@ public class FoodpalApplicationCtrl implements LocaleAware {
if (!recipes.isEmpty()) { if (!recipes.isEmpty()) {
recipeList.getSelectionModel().selectFirst(); 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:
* <ol>
* <li>User creates an untitled recipe</li>
* <li>The application autofocuses on the edit box (not exactly automagically yet)</li>
* <li>User edits the name to whatever they want</li>
* <li>Profit??</li>
* </ol>
*/ */
@FXML @FXML
private void addRecipe() { private void addRecipe() {
// Navigate to "create recipe" screen (should be like a pop-up or new screen or just another place in the app) // a default factory provides the value
mainCtrl.showAddName(); 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 @FXML
@ -191,7 +247,10 @@ public class FoodpalApplicationCtrl implements LocaleAware {
server.deleteRecipe(selected.getId()); 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 @FXML
@ -200,39 +259,42 @@ public class FoodpalApplicationCtrl implements LocaleAware {
if (selected == null) { if (selected == null) {
return; return;
} }
// Let MainCtrl open the full detail screen detailsScreen.visibleProperty().set(true);
mainCtrl.showAddName(); //I had showrecipedetail but intelij says showoverview
} }
/**
* 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 @FXML
private void editRecipeTitle() { private void editRecipeTitle() {
// TODO: someone else todo editableTitleArea.getChildren().clear();
// For now reuse openSelectedRecipe() TextField edit = new TextField();
openSelectedRecipe(); 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 // Language buttons
@FXML @FXML
@ -247,14 +309,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
System.out.println("Recipe printed"); 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" * Clones a recipe, when clicking on the button "clone"
*/ */
@ -264,9 +318,9 @@ public class FoodpalApplicationCtrl implements LocaleAware {
return; return;
} }
server.cloneRecipe(selected.getId()); Recipe cloned = server.cloneRecipe(selected.getId());
refresh(); refresh();
recipeList.getSelectionModel().select(cloned);
} }
} }

View file

@ -15,9 +15,6 @@
*/ */
package client.scenes; package client.scenes;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import jakarta.inject.Inject;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
@ -26,116 +23,28 @@ import javafx.util.Pair;
import java.io.IOException; import java.io.IOException;
public class MainCtrl implements LocaleAware { public class MainCtrl {
private final LocaleManager localeManager;
@FXML @FXML
private Stage primaryStage; private Stage primaryStage;
@FXML
private AddNameCtrl addNameCtrl;
private Scene addName;
@FXML @FXML
private FoodpalApplicationCtrl foodpalCtrl; private FoodpalApplicationCtrl foodpalCtrl;
private Scene foodpal; 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, public void setup(Stage primaryStage,
Pair<AddNameCtrl, Parent> addName, Pair<FoodpalApplicationCtrl, Parent> foodpal) throws IOException, InterruptedException {
Pair<FoodpalApplicationCtrl, Parent> foodpal,
Pair<AddIngredientCtrl, Parent> addIngredient,
Pair<AddStepsCtrl, Parent> addStep) throws IOException, InterruptedException {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
this.addNameCtrl = addName.getKey();
this.addName = new Scene(addName.getValue());
this.foodpalCtrl = foodpal.getKey(); this.foodpalCtrl = foodpal.getKey();
this.foodpal = new Scene(foodpal.getValue()); 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(); showFoodpal();
primaryStage.show(); 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() { public void showFoodpal() {
primaryStage.setTitle("FoodPal"); primaryStage.setTitle("FoodPal");
primaryStage.setScene(foodpal); primaryStage.setScene(foodpal);
foodpalCtrl.refresh(); 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);
}
});
}
} }

View file

@ -1,78 +1,18 @@
package client.scenes.recipe; package client.scenes.recipe;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
/** /**
* A custom ListCell for displaying and editing ingredients in an * A custom {@link OrderedEditableListCell<String>} for displaying and editing ingredients in an
* IngredientList. Allows inline editing of ingredient names. * IngredientList. Allows inline editing of ingredient names.
* *
* @see IngredientListCtrl * @see IngredientListCtrl
*/ */
public class IngredientListCell extends ListCell<String> { public class IngredientListCell extends OrderedEditableListCell<String> {
// TODO: change all this to use actual Ingredient objects // TODO: change all this to use actual Ingredient objects
// and all that :) // and all that :)
@Override @Override
protected void updateItem(String item, boolean empty) { protected String fromInput(TextField inputField) {
super.updateItem(item, empty); return inputField.getText();
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);
} }
} }

View file

@ -1,17 +1,18 @@
package client.scenes.recipe; package client.scenes.recipe;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import com.google.inject.Inject;
import commons.Recipe; import commons.Recipe;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.function.Consumer;
import java.util.function.Function;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.control.ListView.EditEvent; 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. * Controller for the ingredient list view in the recipe detail scene.
* Manages displaying, adding, editing, and deleting ingredients. * 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;
}
/** /**
* <p> * <p>
* The list of ingredients currently displayed. * The list of ingredients currently displayed.
@ -29,7 +36,7 @@ public class IngredientListCtrl implements Initializable {
* As the ingredient list in {@link Recipe} is immutable, * As the ingredient list in {@link Recipe} is immutable,
* this copies that list upon initialization to this mutable list. * this copies that list upon initialization to this mutable list.
* <br> * <br>
* 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. * changes and update the recipe accordingly.
* </p> * </p>
*/ */
@ -38,15 +45,18 @@ public class IngredientListCtrl implements Initializable {
/** /**
* A callback function that is called when the ingredient list is updated. * A callback function that is called when the ingredient list is updated.
*/ */
private Function<List<String>, Void> updateCallback; private Consumer<List<String>> updateCallback;
@FXML @FXML
ListView<String> ingredientListView; public ListView<String> ingredientListView;
@FXML @FXML
Button addIngredientButton; public Label ingredientsLabel;
@FXML @FXML
Button deleteIngredientButton; public Button addIngredientButton;
@FXML
public Button deleteIngredientButton;
/** /**
* Set the recipe and refetch data from it. * 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. * @param callback The function to call upon each update.
*/ */
public void setUpdateCallback(Function<List<String>, Void> callback) { public void setUpdateCallback(Consumer<List<String>> callback) {
this.updateCallback = callback; this.updateCallback = callback;
} }
@ -91,7 +101,7 @@ public class IngredientListCtrl implements Initializable {
private void handleIngredientAdd(ActionEvent event) { private void handleIngredientAdd(ActionEvent event) {
this.ingredients.add("Ingredient " + (this.ingredients.size() + 1)); this.ingredients.add("Ingredient " + (this.ingredients.size() + 1));
this.refresh(); this.refresh();
this.updateCallback.apply(this.ingredients); this.updateCallback.accept(this.ingredients);
var select = this.ingredientListView.getSelectionModel(); var select = this.ingredientListView.getSelectionModel();
select.select(this.ingredients.size() - 1); select.select(this.ingredients.size() - 1);
@ -108,7 +118,7 @@ public class IngredientListCtrl implements Initializable {
this.ingredients.set(index, newValue); this.ingredients.set(index, newValue);
this.refresh(); 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.ingredients.remove(selectedIndex);
this.refresh(); this.refresh();
this.updateCallback.apply(this.ingredients); this.updateCallback.accept(this.ingredients);
} }
@FXML @Override
public void initialize(URL location, ResourceBundle resources) { 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 // TODO: set up communication with the server
// this would probably be best done with the callback (so this class doesn't // this would probably be best done with the callback (so this class doesn't
// interact with the server at all) // interact with the server at all)
this.ingredientListView.setEditable(true); this.ingredientListView.setEditable(true);
this.ingredientListView.setCellFactory( this.ingredientListView.setCellFactory(
list -> { list -> {
return new RecipeStepListCell(); return new IngredientListCell();
}); });
this.ingredientListView.setOnEditCommit(this::handleIngredientEdit); this.ingredientListView.setOnEditCommit(this::handleIngredientEdit);

View file

@ -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<T> extends ListCell<T> {
/**
* 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);
}
}

View file

@ -1,96 +1,15 @@
package client.scenes.recipe; package client.scenes.recipe;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
/** /**
* A custom ListCell for displaying and editing ingredients in an * A custom ListCell for displaying and editing ingredients in an
* RecipeStepList. Allows inline editing of ingredient names. * RecipeStepList. Allows inline editing of ingredient names.
* *
* @see RecipeStepListCtrl * @see RecipeStepListCtrl
*/ */
public class RecipeStepListCell extends ListCell<String> { public class RecipeStepListCell extends OrderedEditableListCell<String> {
/**
* 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());
}
@Override @Override
protected void updateItem(String item, boolean empty) { protected String fromInput(TextField inputField) {
super.updateItem(item, empty); return inputField.getText();
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);
} }
} }

View file

@ -1,17 +1,18 @@
package client.scenes.recipe; package client.scenes.recipe;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import com.google.inject.Inject;
import commons.Recipe; import commons.Recipe;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.function.Consumer;
import java.util.function.Function;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.control.ListView.EditEvent; import javafx.scene.control.ListView.EditEvent;
@ -21,7 +22,16 @@ import javafx.scene.control.ListView.EditEvent;
* *
* @see RecipeStepListCell The custom cell implementation. * @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;
}
/** /**
* <p> * <p>
* The list of recipe steps currently displayed. * 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, * As the step list in {@link Recipe} is immutable,
* this copies that list upon initialization to this mutable list. * this copies that list upon initialization to this mutable list.
* <br> * <br>
* 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. * changes and update the recipe accordingly.
* </p> * </p>
*/ */
@ -38,7 +48,7 @@ public class RecipeStepListCtrl implements Initializable {
/** /**
* A callback function that is called when the step list is updated. * A callback function that is called when the step list is updated.
*/ */
private Function<List<String>, Void> updateCallback; private Consumer<List<String>> updateCallback;
@FXML @FXML
ListView<String> recipeStepListView; ListView<String> recipeStepListView;
@ -72,7 +82,7 @@ public class RecipeStepListCtrl implements Initializable {
* *
* @param callback The function to call upon each update. * @param callback The function to call upon each update.
*/ */
public void setUpdateCallback(Function<List<String>, Void> callback) { public void setUpdateCallback(Consumer<List<String>> callback) {
this.updateCallback = callback; this.updateCallback = callback;
} }
@ -91,7 +101,7 @@ public class RecipeStepListCtrl implements Initializable {
private void handleIngredientAdd(ActionEvent event) { private void handleIngredientAdd(ActionEvent event) {
this.steps.add("Step " + (this.steps.size() + 1)); this.steps.add("Step " + (this.steps.size() + 1));
this.refresh(); this.refresh();
this.updateCallback.apply(this.steps); this.updateCallback.accept(this.steps);
var select = this.recipeStepListView.getSelectionModel(); var select = this.recipeStepListView.getSelectionModel();
select.select(this.steps.size() - 1); select.select(this.steps.size() - 1);
@ -108,7 +118,7 @@ public class RecipeStepListCtrl implements Initializable {
this.steps.set(index, newValue); this.steps.set(index, newValue);
this.refresh(); 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.steps.remove(selectedIndex);
this.refresh(); this.refresh();
this.updateCallback.apply(this.steps); this.updateCallback.accept(this.steps);
} }
@FXML @Override
public void initialize(URL location, ResourceBundle resources) { public void updateText() {
// TODO: set up communication with the server stepsLabel.setText(getLocaleString("menu.label.preparation"));
// this would probably be best done with the callback (so this class doesn't addStepButton.setText(getLocaleString("menu.button.add.step"));
// interact with the server at all) deleteStepButton.setText(getLocaleString("menu.button.remove.step"));
}
@Override
public LocaleManager getLocaleManager() {
return localeManager;
}
@Override
public void initializeComponents() {
this.recipeStepListView.setEditable(true); this.recipeStepListView.setEditable(true);
this.recipeStepListView.setCellFactory( this.recipeStepListView.setCellFactory(
list -> { list -> {
return new RecipeStepListCell(); return new RecipeStepListCell();
}); });
this.recipeStepListView.setOnEditCommit(this::handleIngredientEdit); this.recipeStepListView.setOnEditCommit(this::handleIngredientEdit);
this.addStepButton.setOnAction(this::handleIngredientAdd); this.addStepButton.setOnAction(this::handleIngredientAdd);
this.deleteStepButton.setOnAction(this::handleIngredientDelete); this.deleteStepButton.setOnAction(this::handleIngredientDelete);

View file

@ -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());
}
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddIngredientCtrl">
<children>
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
<TextField fx:id="ingredient" layoutX="120.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
<Label fx:id="ingredientLabel" layoutX="28.0" layoutY="29.0" text="Ingredient Name" />
</children>
</AnchorPane>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddNameCtrl">
<children>
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
<TextField fx:id="recipeName" layoutX="109.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
<Label fx:id="recipeNameLabel" layoutX="28.0" layoutY="29.0" text="Recipe Name" />
</children>
</AnchorPane>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddStepsCtrl">
<children>
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
<TextField fx:id="preparationStep" layoutX="120.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
<Label fx:id="preparationStepLabel" layoutX="28.0" layoutY="29.0" text="Preparation step" />
</children>
</AnchorPane>

View file

@ -101,52 +101,25 @@
<!-- CENTER: RECIPE DETAILS --> <!-- CENTER: RECIPE DETAILS -->
<center> <center>
<VBox spacing="20"> <VBox spacing="20" visible="false" fx:id="detailsScreen">
<padding> <padding>
<Insets bottom="10" left="10" right="10" top="10" /> <Insets bottom="10" left="10" right="10" top="10" />
</padding> </padding>
<!-- Recipe title row --> <!-- Recipe title row -->
<HBox spacing="10"> <HBox spacing="10">
<Label fx:id="recipeNameLabel" text="Recipe Name"> <HBox fx:id="editableTitleArea">
<font> </HBox>
<Font name="System Bold" size="14.0" />
</font></Label>
<Button fx:id="editRecipeTitleButton" onAction="#editRecipeTitle" text="Edit" /> <Button fx:id="editRecipeTitleButton" onAction="#editRecipeTitle" text="Edit" />
<Button fx:id="removeRecipeButton2" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" /> <Button fx:id="removeRecipeButton2" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#makePrintable" text="Print Recipe" /> <Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#makePrintable" text="Print Recipe" />
</HBox> </HBox>
<!-- Ingredients --> <!-- Ingredients -->
<VBox spacing="10"> <fx:include source="recipe/IngredientList.fxml" />
<Label fx:id="ingredientsLabel" text="Ingredients">
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<ListView fx:id="ingredientsListView" prefHeight="167.0" prefWidth="912.0" />
<Button fx:id="addIngredientButton" onAction="#addIngredient" text="Add Ingredient" />
</VBox>
<!-- Preparation --> <!-- Preparation -->
<Label fx:id="preparationLabel" text="Preparation"> <fx:include source="recipe/RecipeStepList.fxml" />
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<ListView fx:id="preparationListView" prefHeight="200.0" />
<Button fx:id="addPreparationStepButton" onAction="#addPreparationStep" text="Add Step" />
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="890.6666870117188" minWidth="10.0" prefWidth="854.6666870117188" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="445.3333740234375" minWidth="10.0" prefWidth="57.33331298828125" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
</VBox> </VBox>
</center> </center>

View file

@ -9,13 +9,13 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<AnchorPane xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.recipe.RecipeStepListCtrl"> <AnchorPane xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.recipe.IngredientListCtrl">
<children> <children>
<VBox minHeight="200.0" minWidth="200.0"> <VBox minHeight="200.0" minWidth="200.0">
<children> <children>
<HBox alignment="CENTER_LEFT" spacing="20.0"> <HBox alignment="CENTER_LEFT" spacing="20.0">
<children> <children>
<Label text="Ingredients"> <Label fx:id="ingredientsLabel" text="Ingredients">
<padding> <padding>
<Insets bottom="5.0" top="5.0" /> <Insets bottom="5.0" top="5.0" />
</padding> </padding>

View file

@ -15,7 +15,7 @@
<children> <children>
<HBox alignment="CENTER_LEFT" spacing="20.0"> <HBox alignment="CENTER_LEFT" spacing="20.0">
<children> <children>
<Label text="Steps"> <Label fx:id="stepsLabel" text="Steps">
<padding> <padding>
<Insets bottom="5.0" top="5.0" /> <Insets bottom="5.0" top="5.0" />
</padding> </padding>

View file

@ -15,7 +15,6 @@
*/ */
package client.scenes; package client.scenes;
import client.utils.LocaleManager;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -24,7 +23,7 @@ public class MainCtrlTest {
@BeforeEach @BeforeEach
public void setup() { public void setup() {
sut = new MainCtrl(new LocaleManager()); sut = new MainCtrl();
} }
@Test @Test