Merge branch 'fix/favourites-rendering' into 'main'
fix: favourites rendering properly after a list change propagated from server. See merge request cse1105/2025-2026/teams/csep-team-76!89
This commit is contained in:
commit
bfe6505c54
1 changed files with 24 additions and 130 deletions
|
|
@ -2,12 +2,10 @@ package client.scenes;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import client.exception.InvalidModificationException;
|
import client.exception.InvalidModificationException;
|
||||||
import client.scenes.nutrition.NutritionViewCtrl;
|
import client.scenes.nutrition.NutritionViewCtrl;
|
||||||
|
|
@ -21,7 +19,6 @@ import client.utils.LocaleManager;
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import client.utils.WebSocketDataService;
|
import client.utils.WebSocketDataService;
|
||||||
import client.utils.WebSocketUtils;
|
import client.utils.WebSocketUtils;
|
||||||
import commons.Ingredient;
|
|
||||||
import commons.Recipe;
|
import commons.Recipe;
|
||||||
|
|
||||||
import commons.ws.Topics;
|
import commons.ws.Topics;
|
||||||
|
|
@ -32,13 +29,16 @@ import commons.ws.messages.Message;
|
||||||
import commons.ws.messages.UpdateRecipeMessage;
|
import commons.ws.messages.UpdateRecipeMessage;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ListProperty;
|
||||||
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
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;
|
||||||
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.TextInputDialog;
|
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
|
@ -66,8 +66,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
public ListView<Recipe> recipeList;
|
public ListView<Recipe> recipeList;
|
||||||
|
|
||||||
@FXML
|
private final ListProperty<Recipe> favouriteRecipeList = new SimpleListProperty<>();
|
||||||
private ListView<Ingredient> ingredientListView;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button addRecipeButton;
|
private Button addRecipeButton;
|
||||||
|
|
@ -88,8 +87,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
private Button manageIngredientsButton;
|
private Button manageIngredientsButton;
|
||||||
|
|
||||||
private List<Recipe> allRecipes = new ArrayList<>();
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label updatedBadge;
|
private Label updatedBadge;
|
||||||
|
|
||||||
|
|
@ -287,6 +284,13 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
openSelectedRecipe();
|
openSelectedRecipe();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
recipeList.getItems().addListener((ListChangeListener.Change<? extends Recipe> c) -> {
|
||||||
|
favouriteRecipeList.set(
|
||||||
|
FXCollections.observableList(
|
||||||
|
recipeList.getItems().stream().filter(r -> config.isFavourite(r.getId())).toList()
|
||||||
|
));
|
||||||
|
System.out.println(favouriteRecipeList);
|
||||||
|
});
|
||||||
|
|
||||||
this.initializeSearchBar();
|
this.initializeSearchBar();
|
||||||
refresh();
|
refresh();
|
||||||
|
|
@ -325,8 +329,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
logger.severe(msg);
|
logger.severe(msg);
|
||||||
printError(msg);
|
printError(msg);
|
||||||
}
|
}
|
||||||
|
recipeList.getItems().setAll(recipes);
|
||||||
allRecipes = new ArrayList<>(recipes);
|
|
||||||
applyRecipeFilterAndKeepSelection();
|
applyRecipeFilterAndKeepSelection();
|
||||||
|
|
||||||
showUpdatedBadge();
|
showUpdatedBadge();
|
||||||
|
|
@ -422,16 +425,20 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
public void applyRecipeFilterAndKeepSelection() {
|
public void applyRecipeFilterAndKeepSelection() {
|
||||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||||
Long selectedId = selected == null ? null : selected.getId();
|
Long selectedId = selected == null ? null : selected.getId();
|
||||||
|
List<Recipe> view = recipeList.getItems().stream().toList();
|
||||||
List<Recipe> view = allRecipes;
|
|
||||||
|
|
||||||
if (favouritesOnlyToggle != null && favouritesOnlyToggle.isSelected()) {
|
if (favouritesOnlyToggle != null && favouritesOnlyToggle.isSelected()) {
|
||||||
view = allRecipes.stream()
|
view = favouriteRecipeList.get();
|
||||||
.filter(r -> config.isFavourite(r.getId()))
|
recipeList.getItems().setAll(view);
|
||||||
.collect(Collectors.toList());
|
}
|
||||||
|
try {
|
||||||
|
if (favouritesOnlyToggle != null && !favouritesOnlyToggle.isSelected()) {
|
||||||
|
recipeList.getItems().setAll(server.getRecipes(config.getRecipeLanguages()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException e) {
|
||||||
|
logger.severe(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeList.getItems().setAll(view);
|
|
||||||
|
|
||||||
// restore selection if possible
|
// restore selection if possible
|
||||||
if (selectedId != null) {
|
if (selectedId != null) {
|
||||||
|
|
@ -452,119 +459,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
this.recipeDetailController.refreshFavouriteButton();
|
this.recipeDetailController.refreshFavouriteButton();
|
||||||
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
//Delete Ingredient button click
|
|
||||||
@FXML
|
|
||||||
private void handleDeleteIngredient() {
|
|
||||||
// Get selected ingredient
|
|
||||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
|
||||||
|
|
||||||
if (selectedIngredient == null) {
|
|
||||||
// Show an error message if no ingredient is selected
|
|
||||||
showError("No ingredient selected", "Please select an ingredient to delete.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the ingredient is used in any recipe
|
|
||||||
checkIngredientUsage(selectedIngredient);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ingredient is used in any recipe before deleting
|
|
||||||
private void checkIngredientUsage(Ingredient ingredient) {
|
|
||||||
try {
|
|
||||||
long usageCount = server.getIngredientUsage(ingredient.getId()); // Check ingredient usage via ServerUtils
|
|
||||||
|
|
||||||
if (usageCount > 0) {
|
|
||||||
// If ingredient is used, show a warning dialog
|
|
||||||
showWarningDialog(ingredient, usageCount);
|
|
||||||
} else {
|
|
||||||
// If not used, delete
|
|
||||||
deleteIngredient(ingredient);
|
|
||||||
}
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
showError("Error", "Failed to check ingredient usage: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteIngredient(Ingredient ingredient) {
|
|
||||||
try {
|
|
||||||
server.deleteIngredient(ingredient.getId()); // Call ServerUtils to delete the ingredient
|
|
||||||
showConfirmation("Success", "Ingredient '" + ingredient.getName() + "' has been deleted.");
|
|
||||||
refreshIngredientList(); // refresh the ingredient list
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
showError("Error", "Failed to delete ingredient: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showWarningDialog(Ingredient ingredient, long usedInRecipes) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showError(String title, String message) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
|
||||||
alert.setTitle(title);
|
|
||||||
alert.setHeaderText(null);
|
|
||||||
alert.setContentText(message);
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showConfirmation(String title, String message) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
|
||||||
alert.setTitle(title);
|
|
||||||
alert.setHeaderText(null);
|
|
||||||
alert.setContentText(message);
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshIngredientList() {
|
|
||||||
// Refresh
|
|
||||||
ingredientListView.getItems().clear();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void handleAddIngredient() {
|
|
||||||
//ask the user for the ingredient name
|
|
||||||
TextInputDialog dialog = new TextInputDialog();
|
|
||||||
dialog.setTitle("Add Ingredient");
|
|
||||||
dialog.setHeaderText("Enter the ingredient name:");
|
|
||||||
dialog.setContentText("Ingredient:");
|
|
||||||
|
|
||||||
// Wait for the user to enter a value
|
|
||||||
Optional<String> result = dialog.showAndWait();
|
|
||||||
|
|
||||||
result.ifPresent(recipeName ->{
|
|
||||||
String trim = recipeName.trim();
|
|
||||||
if(trim.isEmpty()){
|
|
||||||
showError("Invalid Input", "Cannot have empty ingredient name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(ingredientListView.getItems().stream()
|
|
||||||
.map(Ingredient::getName)
|
|
||||||
.anyMatch(ingName -> ingName.equalsIgnoreCase(trim))){
|
|
||||||
showError("Duplicate Ingredient", "Cannot have duplicate ingredients," +
|
|
||||||
" Please provide unique Ingredient names");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// display ingredient name
|
|
||||||
@FXML
|
|
||||||
private void initializeIngredients() {
|
|
||||||
ingredientListView.setCellFactory(list -> new ListCell<Ingredient>() {
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Ingredient item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
if (empty || item == null) {
|
|
||||||
setText(null);
|
|
||||||
} else {
|
|
||||||
setText(item.getName()); // Display the ingredient name in the ListView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void openIngredientsPopup() {
|
private void openIngredientsPopup() {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue