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:
commit
6129fc2a5a
21 changed files with 335 additions and 794 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package client.exception;
|
||||
|
||||
public class UpdateException extends RuntimeException {
|
||||
public UpdateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String> ingredientsListView;
|
||||
|
||||
@FXML
|
||||
private Button addIngredientButton;
|
||||
|
||||
@FXML
|
||||
private ListView<String> 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<Recipe> 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:
|
||||
* <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
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AddNameCtrl, Parent> addName,
|
||||
Pair<FoodpalApplicationCtrl, Parent> foodpal,
|
||||
Pair<AddIngredientCtrl, Parent> addIngredient,
|
||||
Pair<AddStepsCtrl, Parent> addStep) throws IOException, InterruptedException {
|
||||
Pair<FoodpalApplicationCtrl, Parent> 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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>} for displaying and editing ingredients in an
|
||||
* IngredientList. Allows inline editing of ingredient names.
|
||||
*
|
||||
* @see IngredientListCtrl
|
||||
*/
|
||||
public class IngredientListCell extends ListCell<String> {
|
||||
public class IngredientListCell extends OrderedEditableListCell<String> {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* <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.
|
||||
* </p>
|
||||
*/
|
||||
|
|
@ -38,15 +45,18 @@ public class IngredientListCtrl implements Initializable {
|
|||
/**
|
||||
* A callback function that is called when the ingredient list is updated.
|
||||
*/
|
||||
private Function<List<String>, Void> updateCallback;
|
||||
private Consumer<List<String>> updateCallback;
|
||||
|
||||
@FXML
|
||||
ListView<String> ingredientListView;
|
||||
public ListView<String> 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<List<String>, Void> callback) {
|
||||
public void setUpdateCallback(Consumer<List<String>> 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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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());
|
||||
}
|
||||
|
||||
public class RecipeStepListCell extends OrderedEditableListCell<String> {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* <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.
|
||||
* </p>
|
||||
*/
|
||||
|
|
@ -38,7 +48,7 @@ public class RecipeStepListCtrl implements Initializable {
|
|||
/**
|
||||
* A callback function that is called when the step list is updated.
|
||||
*/
|
||||
private Function<List<String>, Void> updateCallback;
|
||||
private Consumer<List<String>> updateCallback;
|
||||
|
||||
@FXML
|
||||
ListView<String> recipeStepListView;
|
||||
|
|
@ -72,7 +82,7 @@ public class RecipeStepListCtrl implements Initializable {
|
|||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
15
client/src/main/java/client/utils/DefaultRecipeFactory.java
Normal file
15
client/src/main/java/client/utils/DefaultRecipeFactory.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -101,52 +101,25 @@
|
|||
|
||||
<!-- CENTER: RECIPE DETAILS -->
|
||||
<center>
|
||||
<VBox spacing="20">
|
||||
<VBox spacing="20" visible="false" fx:id="detailsScreen">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
|
||||
<!-- Recipe title row -->
|
||||
<HBox spacing="10">
|
||||
<Label fx:id="recipeNameLabel" text="Recipe Name">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font></Label>
|
||||
<HBox fx:id="editableTitleArea">
|
||||
</HBox>
|
||||
<Button fx:id="editRecipeTitleButton" onAction="#editRecipeTitle" text="Edit" />
|
||||
<Button fx:id="removeRecipeButton2" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
||||
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#makePrintable" text="Print Recipe" />
|
||||
</HBox>
|
||||
|
||||
<!-- Ingredients -->
|
||||
<VBox spacing="10">
|
||||
<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>
|
||||
<fx:include source="recipe/IngredientList.fxml" />
|
||||
|
||||
<!-- Preparation -->
|
||||
<Label fx:id="preparationLabel" text="Preparation">
|
||||
<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>
|
||||
<fx:include source="recipe/RecipeStepList.fxml" />
|
||||
</VBox>
|
||||
</center>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
<?import javafx.scene.layout.VBox?>
|
||||
<?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>
|
||||
<VBox minHeight="200.0" minWidth="200.0">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||
<children>
|
||||
<Label text="Ingredients">
|
||||
<Label fx:id="ingredientsLabel" text="Ingredients">
|
||||
<padding>
|
||||
<Insets bottom="5.0" top="5.0" />
|
||||
</padding>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||
<children>
|
||||
<Label text="Steps">
|
||||
<Label fx:id="stepsLabel" text="Steps">
|
||||
<padding>
|
||||
<Insets bottom="5.0" top="5.0" />
|
||||
</padding>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleManager;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ public class MainCtrlTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
sut = new MainCtrl(new LocaleManager());
|
||||
sut = new MainCtrl();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue