merge main into feature/client-side-search

This commit is contained in:
Natalia Cholewa 2025-12-19 23:43:39 +01:00
commit 96eb19d74c
18 changed files with 528 additions and 138 deletions

View file

@ -4,7 +4,6 @@ package client.scenes;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import client.exception.UpdateException;
import client.scenes.recipe.IngredientListCtrl;
@ -23,7 +22,6 @@ import commons.ws.Topics;
import commons.ws.messages.Message;
import jakarta.inject.Inject;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
@ -48,18 +46,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
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
@FXML
private Button flagNlButton; //already here for advanced stuff
@FXML
private Button flagPlButton; //already here for advanced stuff
// everything in the left lane
@FXML
public Label recipesLabel;
@ -113,6 +99,23 @@ public class FoodpalApplicationCtrl implements LocaleAware {
initializeWebSocket();
}
private void initRecipeList() {
// Show recipe name in the list
recipeList.setCellFactory(list -> new ListCell<>() {
@Override
protected void updateItem(Recipe item, boolean empty) {
super.updateItem(item, empty);
setText(empty || item == null ? "" : item.getName());
}
});
// When your selection changes, update details in the panel
recipeList.getSelectionModel().selectedItemProperty().addListener(
(obs, oldRecipe, newRecipe) -> {
showRecipeDetails(newRecipe);
updateFavouriteButton(newRecipe);
}
);
}
@Inject
void setSearchBarCtrl(SearchBarCtrl searchBarCtrl) {
@ -165,11 +168,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
});
});
}
@Override
public void initializeComponents() {
config = configService.getConfig();
// TODO Reduce code duplication??
private void initStepsIngredientsList() {
// Initialize callback for ingredient list updates
this.ingredientListCtrl.setUpdateCallback(newList -> {
Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem();
@ -199,21 +198,12 @@ public class FoodpalApplicationCtrl implements LocaleAware {
throw new UpdateException("Unable to update recipe to server for " + selectedRecipe);
}
});
// Show recipe name in the list
recipeList.setCellFactory(list -> new ListCell<>() {
@Override
protected void updateItem(Recipe item, boolean empty) {
super.updateItem(item, empty);
setText(empty || item == null ? "" : item.getName());
}
});
// When your selection changes, update details in the panel
recipeList.getSelectionModel().selectedItemProperty().addListener(
(obs, oldRecipe, newRecipe) -> {
showRecipeDetails(newRecipe);
updateFavouriteButton(newRecipe);
}
);
}
@Override
public void initializeComponents() {
config = configService.getConfig();
initStepsIngredientsList();
initRecipeList();
// Double-click to go to detail screen
recipeList.setOnMouseClicked(event -> {
@ -222,7 +212,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
openSelectedRecipe();
}
});
this.initializeSearchBar();
refresh();
updateFavouriteButton(recipeList.getSelectionModel().getSelectedItem());
@ -247,13 +236,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
printRecipeButton.setText(getLocaleString("menu.button.print"));
recipesLabel.setText(getLocaleString("menu.label.recipes"));
// 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
@ -378,15 +360,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
editableTitleArea.getChildren().add(edit);
edit.requestFocus();
}
// Language buttons
@FXML
private void switchLocale(ActionEvent event) {
Button button = (Button)event.getSource();
String lang = (String)button.getUserData();
localeManager.setLocale(Locale.of(lang));
}
@FXML
private void makePrintable() {
System.out.println("Recipe printed");

View file

@ -0,0 +1,100 @@
package client.scenes;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import com.google.inject.Inject;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
import java.io.InputStream;
import java.util.Locale;
/**
* The language selection menu controller.
* This needs to implement {@link LocaleAware LocaleAware} so that the
* <code>getLocaleString(String)</code> function is available.
*/
public class LangSelectMenuCtrl implements LocaleAware {
public ComboBox<String> langSelectMenu;
private final LocaleManager manager;
@Inject
public LangSelectMenuCtrl(LocaleManager manager) {
this.manager = manager;
}
/**
* Switches the locale of the application depending on which button the user selects in the list.
* @param event The action event triggered by the user.
*/
@FXML
private void switchLocale(ActionEvent event) {
String lang = langSelectMenu.getSelectionModel().getSelectedItem();
manager.setLocale(Locale.of(lang));
}
@Override
public void initializeComponents() {
langSelectMenu.getItems().setAll("en", "pl", "nl");
langSelectMenu.setConverter(new StringConverter<String>() {
@Override
public String toString(String s) {
if (s == null) {
return "";
}
return getLocaleString("lang." + s + ".display");
}
@Override
public String fromString(String s) {
return s;
}
});
langSelectMenu.setCellFactory(list -> new ListCell<>() {
@Override
protected void updateItem(String item, boolean empty) {
final int IMAGE_HEIGHT = 32;
final int HBOX_SPACING = 10;
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
return;
}
InputStream imageStream = getClass().getResourceAsStream("/flag_" + item + ".png");
if (imageStream == null) {
setGraphic(new HBox(new Label(getLocaleString("lang." + item + ".display"))));
return;
}
Image img = new Image(imageStream);
ImageView imageView = new ImageView(img);
imageView.setFitHeight(IMAGE_HEIGHT);
imageView.setFitWidth(IMAGE_HEIGHT);
setGraphic(
new HBox(
HBOX_SPACING,
imageView,
new Label(getLocaleString("lang." + item + ".display"))
)
);
}
});
}
@Override
public void updateText() {
// doesn't do anything; the text doesn't need to be updated.
}
@Override
public LocaleManager getLocaleManager() {
return manager;
}
}

View file

@ -0,0 +1,67 @@
package client.utils;
import commons.Recipe;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class PrintExportService {
/**
* Builds the String with all the recipe data in a human-readable format and returns said string.
* @param recipe - Recipe Object that needs to be converted
* @return - String Result that is the converted recipe object
*/
public static String buildRecipeText(Recipe recipe){
String result = "Title: " + recipe.getName() + "\nRecipe ID: " + recipe.getId() + "\n" +
"Ingredients: "; //Starts the string with name and recipe ID
for(int i =0; i<recipe.getIngredients().size();i++){ // For loop adding ingredients one by one
result += recipe.getIngredients().get(i) + ", ";
}
result += "\nSteps:\n";
for(int i =0; i<recipe.getPreparationSteps().size();i++){ // Preparation Steps separated by new lines
result += (i+1) + ": " + recipe.getPreparationSteps().get(i) + "\n";
}
return result;
}
/**
* Method that checks if the directory path of the selected directory is valid and throws and error.
* if not valid
* @param path - Path to directory
*/
public static void validateFolder(Path path){
if (path == null) { //Null path value
throw new IllegalArgumentException("Path is empty");
}
if(!Files.exists(path)){ //If Folder doesn't exist
throw new IllegalArgumentException("Folder does not exist");
}
if(!Files.isDirectory(path)){ // If folder is not directory
throw new IllegalArgumentException("Given path is not a folder");
}
}
/**
* Method that exports a plain-text String of recipe data, into a newly created file from
* a selected directory provided as a path to the method and names said file based on the
* nameOfFile param.
* @param recipeData - String containing recipe data that will be written to file
* @param dirFilePath - Path to Folder
* @param nameOfFile - Name of file to be created
*/
public static void exportToFile(String recipeData, Path dirFilePath, String nameOfFile){
validateFolder(dirFilePath); //runs validate Folder method to check if Directory path is valid
Path filePath = dirFilePath.resolve(nameOfFile); //Creates a file with name provided in directory and stores path in PATH object
try {
Files.writeString(filePath, recipeData); // Writes the recipe data into the File.
}
catch (IOException e){
System.err.println("An error occurred while writing to the file");
}
}
}

View file

@ -15,6 +15,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.control.ComboBox?>
<BorderPane prefHeight="800.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.FoodpalApplicationCtrl">
<!-- TOP BAR -->
@ -23,58 +24,12 @@
<padding>
<Insets bottom="10" left="10" right="10" top="10" />
</padding>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="198.0" minWidth="10.0" prefWidth="130.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="2.0" prefWidth="70.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="54.0" minHeight="10.0" prefHeight="54.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="25.0" minHeight="6.0" prefHeight="6.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="56.0" prefWidth="158.0" text="FoodPal">
<font>
<Font name="System Bold" size="29.0" />
</font>
</Label>
<ButtonBar prefHeight="40.0" prefWidth="200.0" GridPane.rowIndex="2">
<buttons>
<ToolBar prefHeight="35.0" prefWidth="123.0">
<items>
<Button fx:id="flagEnButton" minWidth="33.0" onAction="#switchLocale" userData="en" prefHeight="25.0" prefWidth="33.0" text="EN" />
<Button fx:id="flagNlButton" onAction="#switchLocale" userData="nl" text="NL" />
<Button fx:id="flagPlButton" onAction="#switchLocale" userData="pl" text="PL" />
</items>
</ToolBar>
</buttons>
</ButtonBar>
</children>
</GridPane>
<Pane HBox.hgrow="ALWAYS" />
<HBox spacing="5" />
<GridPane prefHeight="90.0" prefWidth="114.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="10.0" prefWidth="32.1666259765625" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="10.0" prefWidth="24.8333740234375" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="173.0" minWidth="10.0" prefWidth="28.6666259765625" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="34.0" />
</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>
<children>
<Button fx:id="refreshButton" onAction="#refresh" prefHeight="25.0" prefWidth="34.0" text="⟳" GridPane.columnIndex="3" GridPane.rowIndex="2" />
</children>
</GridPane>
</HBox>
<Label prefHeight="56.0" prefWidth="158.0" text="FoodPal">
<font>
<Font name="System Bold" size="29.0" />
</font>
</Label>
</HBox>
</top>
<!-- LEFT: RECIPE LIST -->
@ -83,7 +38,8 @@
<padding>
<Insets bottom="10" left="10" right="10" top="10" />
</padding>
<fx:include source="LangSelect.fxml" />
<Button fx:id="refreshButton" onAction="#refresh" prefHeight="25.0" prefWidth="34.0" text="⟳" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<Label fx:id="recipesLabel" text="Recipes">
<font>
<Font name="System Bold" size="15.0" />

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<ComboBox
fx:id="langSelectMenu"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
onAction="#switchLocale"
fx:controller="client.scenes.LangSelectMenuCtrl"
>
</ComboBox>

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

View file

@ -24,3 +24,7 @@ menu.button.remove.step=Remove Step
menu.button.edit=Edit
menu.button.clone=Clone
menu.button.print=Print recipe
lang.en.display=English
lang.nl.display=Dutch
lang.pl.display=Polish

View file

@ -25,4 +25,7 @@ menu.button.edit=Edit
menu.button.clone=Clone
menu.button.print=Print recipe
menu.search=Search...
menu.search=Search...
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski

View file

@ -25,4 +25,7 @@ menu.button.edit=Bewerken
menu.button.clone=Dupliceren
menu.button.print=Recept afdrukken
menu.search=Zoeken...
menu.search=Zoeken...
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski

View file

@ -25,4 +25,7 @@ menu.button.edit=Edytuj
menu.button.clone=Duplikuj
menu.button.print=Drukuj przepis
menu.search=Szukaj...
menu.search=Szukaj...
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski