feat: new UX for add/edit/update recipes

Revised adding recipes to have untitled recipes with inline name editing
(name edit box autofocused upon recipe creation)
This commit is contained in:
Zhongheng Liu 2025-12-04 13:34:28 +01:00
commit d42e6ff74c
Signed by: steven
GPG key ID: F69B980899C1C09D
3 changed files with 96 additions and 12 deletions

View file

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

View file

@ -4,20 +4,30 @@ package client.scenes;
import java.io.IOException;
import java.util.List;
import client.exception.UpdateException;
import client.utils.DefaultRecipeFactory;
import client.utils.ServerUtils;
import commons.Recipe;
import jakarta.inject.Inject;
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;
public class FoodpalApplicationCtrl {
private final MainCtrl mainCtrl;
private final ServerUtils server;
public VBox detailsScreen;
public HBox editableTitleArea;
// all of these aren't used with only my part of the code
// everything in the top bar ===
@ -103,10 +113,13 @@ public class FoodpalApplicationCtrl {
refresh();
}
private void showName(String name) {
editableTitleArea.getChildren().clear();
editableTitleArea.getChildren().add(new Label(name));
}
// till the all the code from everyone is implemented for now to not have errors
private void showRecipeDetails(Recipe newRecipe) {
recipeNameLabel.setText(newRecipe.getName());
showName(newRecipe.getName());
ingredientsListView.getItems().setAll(newRecipe.getIngredients());
preparationListView.getItems().setAll(newRecipe.getPreparationSteps());
@ -123,17 +136,40 @@ public class FoodpalApplicationCtrl {
// Select first recipe in the list by default
if (!recipes.isEmpty()) {
recipeList.getSelectionModel().selectFirst();
detailsScreen.visibleProperty().set(true);
}
}
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
@ -145,7 +181,10 @@ public class FoodpalApplicationCtrl {
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
@ -154,15 +193,38 @@ public class FoodpalApplicationCtrl {
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();
} 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);
}
/**

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