Merge branch 'client/lang-button-dropdown' into 'main'

feat: Client language switching as dropdown menu

Closes #18

See merge request cse1105/2025-2026/teams/csep-team-76!21
This commit is contained in:
Zhongheng Liu 2025-12-19 22:57:31 +01:00
commit 5db8721148
11 changed files with 172 additions and 110 deletions

View file

@ -4,7 +4,6 @@ package client.scenes;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import client.exception.UpdateException; import client.exception.UpdateException;
import client.scenes.recipe.IngredientListCtrl; import client.scenes.recipe.IngredientListCtrl;
@ -23,7 +22,6 @@ import commons.ws.Topics;
import commons.ws.messages.Message; import commons.ws.messages.Message;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@ -46,18 +44,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
public VBox detailsScreen; public VBox detailsScreen;
public HBox editableTitleArea; 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 // everything in the left lane
@FXML @FXML
public Label recipesLabel; public Label recipesLabel;
@ -110,7 +96,31 @@ public class FoodpalApplicationCtrl implements LocaleAware {
initializeWebSocket(); 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);
}
);
// Double-click to go to detail screen
recipeList.setOnMouseClicked(event -> {
final int DOUBLE_CLICK = 2; //to not get magic number:P
if (event.getClickCount() == DOUBLE_CLICK) {
openSelectedRecipe();
}
});
}
private void initializeWebSocket() { private void initializeWebSocket() {
webSocketUtils.connect(() -> { webSocketUtils.connect(() -> {
webSocketUtils.subscribe(Topics.RECIPES, (Message _) -> { webSocketUtils.subscribe(Topics.RECIPES, (Message _) -> {
@ -132,11 +142,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
}); });
}); });
} }
private void initStepsIngredientsList() {
@Override
public void initializeComponents() {
config = configService.getConfig();
// TODO Reduce code duplication??
// Initialize callback for ingredient list updates // Initialize callback for ingredient list updates
this.ingredientListCtrl.setUpdateCallback(newList -> { this.ingredientListCtrl.setUpdateCallback(newList -> {
Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem(); Recipe selectedRecipe = recipeList.getSelectionModel().getSelectedItem();
@ -166,29 +172,13 @@ public class FoodpalApplicationCtrl implements LocaleAware {
throw new UpdateException("Unable to update recipe to server for " + selectedRecipe); throw new UpdateException("Unable to update recipe to server for " + selectedRecipe);
} }
}); });
// Show recipe name in the list }
recipeList.setCellFactory(list -> new ListCell<>() {
@Override @Override
protected void updateItem(Recipe item, boolean empty) { public void initializeComponents() {
super.updateItem(item, empty); config = configService.getConfig();
setText(empty || item == null ? "" : item.getName()); initStepsIngredientsList();
} initRecipeList();
});
// When your selection changes, update details in the panel
recipeList.getSelectionModel().selectedItemProperty().addListener(
(obs, oldRecipe, newRecipe) -> {
showRecipeDetails(newRecipe);
updateFavouriteButton(newRecipe);
}
);
// Double-click to go to detail screen
recipeList.setOnMouseClicked(event -> {
final int DOUBLE_CLICK = 2; //to not get magic number:P
if (event.getClickCount() == DOUBLE_CLICK) {
openSelectedRecipe();
}
});
refresh(); refresh();
updateFavouriteButton(recipeList.getSelectionModel().getSelectedItem()); updateFavouriteButton(recipeList.getSelectionModel().getSelectedItem());
} }
@ -212,13 +202,6 @@ 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"));
// 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 @Override
@ -343,15 +326,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
editableTitleArea.getChildren().add(edit); editableTitleArea.getChildren().add(edit);
edit.requestFocus(); 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 @FXML
private void makePrintable() { private void makePrintable() {
System.out.println("Recipe printed"); 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

@ -15,6 +15,7 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?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"> <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 --> <!-- TOP BAR -->
@ -23,57 +24,11 @@
<padding> <padding>
<Insets bottom="10" left="10" right="10" top="10" /> <Insets bottom="10" left="10" right="10" top="10" />
</padding> </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"> <Label prefHeight="56.0" prefWidth="158.0" text="FoodPal">
<font> <font>
<Font name="System Bold" size="29.0" /> <Font name="System Bold" size="29.0" />
</font> </font>
</Label> </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> </HBox>
</top> </top>
@ -83,7 +38,8 @@
<padding> <padding>
<Insets bottom="10" left="10" right="10" top="10" /> <Insets bottom="10" left="10" right="10" top="10" />
</padding> </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"> <Label fx:id="recipesLabel" text="Recipes">
<font> <font>
<Font name="System Bold" size="15.0" /> <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.edit=Edit
menu.button.clone=Clone menu.button.clone=Clone
menu.button.print=Print recipe menu.button.print=Print recipe
lang.en.display=English
lang.nl.display=Dutch
lang.pl.display=Polish

View file

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

View file

@ -24,3 +24,7 @@ menu.button.remove.step=Stap verwijderen
menu.button.edit=Bewerken menu.button.edit=Bewerken
menu.button.clone=Dupliceren menu.button.clone=Dupliceren
menu.button.print=Recept afdrukken menu.button.print=Recept afdrukken
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski

View file

@ -24,3 +24,7 @@ menu.button.remove.step=Usuń instrukcję
menu.button.edit=Edytuj menu.button.edit=Edytuj
menu.button.clone=Duplikuj menu.button.clone=Duplikuj
menu.button.print=Drukuj przepis menu.button.print=Drukuj przepis
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski