From 8a278cd6e214cf5800e99523228a761876154c0e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 27 Mar 2026 10:07:37 -0600 Subject: [PATCH 1/2] add desktop pet and product images --- .../petshopdesktop/DTOs/ProductDTO.java | 16 +++- .../example/petshopdesktop/api/ApiClient.java | 2 +- .../petshopdesktop/api/endpoints/PetApi.java | 13 +++ .../api/endpoints/ProductApi.java | 13 +++ .../controllers/PetController.java | 33 ++++++- .../controllers/ProductController.java | 33 ++++++- .../PetDialogController.java | 88 +++++++++++++++++- .../ProductDialogController.java | 89 ++++++++++++++++++- .../example/petshopdesktop/models/Pet.java | 16 +++- .../util/DesktopImageSupport.java | 53 +++++++++++ .../dialogviews/pet-dialog-view.fxml | 17 ++++ .../dialogviews/product-dialog-view.fxml | 21 ++++- .../petshopdesktop/modelviews/pet-view.fxml | 18 ++-- .../modelviews/product-view.fxml | 13 +-- 14 files changed, 402 insertions(+), 23 deletions(-) create mode 100644 desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java index 3ea081df..3270d6ca 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java @@ -15,15 +15,17 @@ public class ProductDTO { private SimpleIntegerProperty categoryId; //used for edit and delete private SimpleStringProperty categoryName; private SimpleStringProperty prodDesc; + private SimpleStringProperty imageUrl; //constructor - public ProductDTO(int prodId, String prodName, double prodPrice, int categoryId, String categoryName, String prodDesc) { + public ProductDTO(int prodId, String prodName, double prodPrice, int categoryId, String categoryName, String prodDesc, String imageUrl) { this.prodId = new SimpleIntegerProperty(prodId); this.prodName = new SimpleStringProperty(prodName); this.prodPrice = new SimpleDoubleProperty(prodPrice); this.categoryId = new SimpleIntegerProperty(categoryId); this.categoryName = new SimpleStringProperty(categoryName); this.prodDesc = new SimpleStringProperty(prodDesc); + this.imageUrl = new SimpleStringProperty(imageUrl); } //getter and setters @@ -99,6 +101,18 @@ public class ProductDTO { this.categoryId.set(categoryId); } + public String getImageUrl() { + return imageUrl.get(); + } + + public SimpleStringProperty imageUrlProperty() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl.set(imageUrl); + } + /** * Converts DTO into product for editing and deleting * @return diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java b/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java index ed2e9fc6..d6b48d21 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java @@ -67,7 +67,7 @@ public class ApiClient { } else if (statusCode == 403) { throw new RuntimeException("Access restricted. You don't have permission to perform this action."); } else if (statusCode == 404) { - throw new RuntimeException("Avatar not found."); + throw new RuntimeException("File not found."); } else { throw new RuntimeException("Request failed with status " + statusCode); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java index b5fe23e9..f96372e8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java @@ -9,6 +9,7 @@ import org.example.petshopdesktop.api.dto.pet.PetResponse; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.List; public class PetApi { @@ -47,6 +48,18 @@ public class PetApi { return apiClient.put("/api/v1/pets/" + id, request, PetResponse.class); } + public PetResponse uploadPetImage(Long id, Path imagePath) throws Exception { + return apiClient.postMultipart("/api/v1/pets/" + id + "/image", "image", imagePath, PetResponse.class); + } + + public void deletePetImage(Long id) throws Exception { + apiClient.delete("/api/v1/pets/" + id + "/image"); + } + + public byte[] getPetImage(Long id) throws Exception { + return apiClient.getBytes("/api/v1/pets/" + id + "/image"); + } + public void deletePets(List ids) throws Exception { apiClient.deleteWithBody("/api/v1/pets", new BulkDeleteRequest(ids)); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java index 5bffd489..4b8c89f4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java @@ -9,6 +9,7 @@ import org.example.petshopdesktop.api.dto.product.ProductResponse; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.List; public class ProductApi { @@ -47,6 +48,18 @@ public class ProductApi { return apiClient.put("/api/v1/products/" + id, request, ProductResponse.class); } + public ProductResponse uploadProductImage(Long id, Path imagePath) throws Exception { + return apiClient.postMultipart("/api/v1/products/" + id + "/image", "image", imagePath, ProductResponse.class); + } + + public void deleteProductImage(Long id) throws Exception { + apiClient.delete("/api/v1/products/" + id + "/image"); + } + + public byte[] getProductImage(Long id) throws Exception { + return apiClient.getBytes("/api/v1/products/" + id + "/image"); + } + public void deleteProducts(List ids) throws Exception { apiClient.deleteWithBody("/api/v1/products", new BulkDeleteRequest(ids)); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java index 55ccb3e1..88928cb5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -6,9 +6,12 @@ import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.api.dto.pet.PetResponse; @@ -16,6 +19,7 @@ import org.example.petshopdesktop.api.endpoints.PetApi; import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController; import org.example.petshopdesktop.models.Pet; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; import java.io.IOException; import java.util.List; @@ -42,6 +46,9 @@ public class PetController { @FXML private TableColumn colPetId; + @FXML + private TableColumn colPetImage; + @FXML private TableColumn colPetName; @@ -134,12 +141,14 @@ public class PetController { tvPets.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE); colPetId.setCellValueFactory(new PropertyValueFactory("petId")); + colPetImage.setCellValueFactory(new PropertyValueFactory("imageUrl")); colPetName.setCellValueFactory(new PropertyValueFactory("petName")); colPetSpecies.setCellValueFactory(new PropertyValueFactory("petSpecies")); colPetBreed.setCellValueFactory(new PropertyValueFactory("petBreed")); colPetAge.setCellValueFactory(new PropertyValueFactory("petAge")); colPetStatus.setCellValueFactory(new PropertyValueFactory("petStatus")); colPetPrice.setCellValueFactory(new PropertyValueFactory("petPrice")); + configureImageColumn(colPetImage); displayPets(); @@ -262,8 +271,30 @@ public class PetController { response.getPetBreed(), response.getPetAge() != null ? response.getPetAge() : 0, response.getPetStatus(), - response.getPetPrice().doubleValue() + response.getPetPrice().doubleValue(), + response.getImageUrl() ); } + private void configureImageColumn(TableColumn column) { + column.setCellFactory(col -> new TableCell<>() { + private final ImageView imageView = new ImageView(); + private final StackPane container = new StackPane(imageView); + { + container.setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null || item.isBlank()) { + setGraphic(null); + return; + } + DesktopImageSupport.loadImageInto(imageView, item, 48, 48); + setGraphic(container); + } + }); + } + } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java index e6168911..053e0105 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java @@ -6,9 +6,12 @@ import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.DTOs.ProductDTO; @@ -16,6 +19,7 @@ import org.example.petshopdesktop.api.dto.product.ProductResponse; import org.example.petshopdesktop.api.endpoints.ProductApi; import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; import java.io.IOException; import java.util.ArrayList; @@ -46,6 +50,9 @@ public class ProductController { @FXML private TableColumn colProductId; + @FXML + private TableColumn colProductImage; + @FXML private TableColumn colProductName; @@ -74,10 +81,12 @@ public class ProductController { tvProducts.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE); //set up table columns colProductId.setCellValueFactory(new PropertyValueFactory("prodId")); + colProductImage.setCellValueFactory(new PropertyValueFactory("imageUrl")); colProductName.setCellValueFactory(new PropertyValueFactory("prodName")); colProductPrice.setCellValueFactory(new PropertyValueFactory("prodPrice")); colProductCategory.setCellValueFactory(new PropertyValueFactory("categoryName")); colProductDesc.setCellValueFactory(new PropertyValueFactory("prodDesc")); + configureImageColumn(colProductImage); displayProduct(); @@ -292,8 +301,30 @@ public class ProductController { response.getProdPrice().doubleValue(), 0, response.getCategoryName(), - response.getProdDesc() + response.getProdDesc(), + response.getImageUrl() ); } + private void configureImageColumn(TableColumn column) { + column.setCellFactory(col -> new TableCell<>() { + private final ImageView imageView = new ImageView(); + private final StackPane container = new StackPane(imageView); + { + container.setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null || item.isBlank()) { + setGraphic(null); + return; + } + DesktopImageSupport.loadImageInto(imageView, item, 48, 48); + setGraphic(container); + } + }); + } + } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java index cd7f2690..3ebcfa5b 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java @@ -6,6 +6,7 @@ import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.*; +import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import org.example.petshopdesktop.Validator; @@ -14,7 +15,10 @@ import org.example.petshopdesktop.api.dto.pet.PetResponse; import org.example.petshopdesktop.api.endpoints.PetApi; import org.example.petshopdesktop.models.Pet; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; +import org.example.petshopdesktop.util.FilePickerSupport; +import java.io.File; import java.math.BigDecimal; public class PetDialogController { @@ -24,6 +28,12 @@ public class PetDialogController { @FXML private Button btnSave; + @FXML + private Button btnChangeImage; + + @FXML + private Button btnRemoveImage; + @FXML private ComboBox cbPetStatus; @@ -33,6 +43,12 @@ public class PetDialogController { @FXML private Label lblPetId; + @FXML + private Label lblImageStatus; + + @FXML + private ImageView imgPetPreview; + @FXML private TextField txtPetAge; @@ -49,6 +65,9 @@ public class PetDialogController { private TextField txtPetSpecies; private String mode = null; + private File selectedImageFile; + private String currentImageUrl; + private boolean removeImageRequested; private ObservableList statusList = FXCollections.observableArrayList( "Available", "Adopted" @@ -73,6 +92,10 @@ public class PetDialogController { closeStage(mouseEvent); } }); + + btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage()); + btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage()); + refreshImagePreview(); } private void buttonSaveClicked(MouseEvent mouseEvent) { @@ -103,7 +126,8 @@ public class PetDialogController { PetRequest request = buildPetRequest(); try { if(mode.equals("Add")) { - PetApi.getInstance().createPet(request); + PetResponse response = PetApi.getInstance().createPet(request); + applyImageChanges(response.getPetId()); } else { String[] parts = lblPetId.getText().split(": "); if (parts.length < 2) { @@ -111,6 +135,7 @@ public class PetDialogController { } Long petId = Long.parseLong(parts[1]); PetApi.getInstance().updatePet(petId, request); + applyImageChanges(petId); } //tell the user operation was successful @@ -175,6 +200,10 @@ public class PetDialogController { txtPetBreed.setText(pet.getPetBreed()); txtPetAge.setText(pet.getPetAge() + ""); txtPetPrice.setText(pet.getPetPrice() + ""); + currentImageUrl = pet.getImageUrl(); + selectedImageFile = null; + removeImageRequested = false; + refreshImagePreview(); //get the right combobox selection for (String status : cbPetStatus.getItems()) { @@ -192,10 +221,67 @@ public class PetDialogController { lblMode.setText(mode + " Pet"); if(mode.equals("Add")) { lblPetId.setVisible(false); + currentImageUrl = null; + selectedImageFile = null; + removeImageRequested = false; + refreshImagePreview(); } else if(mode.equals("Edit")) { lblPetId.setVisible(true); + refreshImagePreview(); } } + private void handleChangeImage() { + File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow()); + if (file == null) { + return; + } + selectedImageFile = file; + removeImageRequested = false; + lblImageStatus.setText("Selected: " + file.getName()); + DesktopImageSupport.loadImageInto(imgPetPreview, file.toURI().toString(), 120, 120); + btnRemoveImage.setDisable(false); + } + + private void handleRemoveImage() { + selectedImageFile = null; + removeImageRequested = true; + currentImageUrl = null; + refreshImagePreview(); + } + + private void applyImageChanges(Long petId) throws Exception { + if (removeImageRequested) { + try { + PetApi.getInstance().deletePetImage(petId); + } catch (Exception ignored) { + } + } + if (selectedImageFile != null) { + PetApi.getInstance().uploadPetImage(petId, selectedImageFile.toPath()); + } + } + + private void refreshImagePreview() { + if (imgPetPreview == null || lblImageStatus == null || btnRemoveImage == null) { + return; + } + imgPetPreview.setImage(null); + if (selectedImageFile != null) { + lblImageStatus.setText("Selected: " + selectedImageFile.getName()); + DesktopImageSupport.loadImageInto(imgPetPreview, selectedImageFile.toURI().toString(), 120, 120); + btnRemoveImage.setDisable(false); + return; + } + if (currentImageUrl != null && !currentImageUrl.isBlank()) { + lblImageStatus.setText("Current image loaded"); + DesktopImageSupport.loadImageInto(imgPetPreview, currentImageUrl, 120, 120); + btnRemoveImage.setDisable(false); + return; + } + lblImageStatus.setText("No image selected"); + btnRemoveImage.setDisable(true); + } + } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java index 25fe1da8..796a8019 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java @@ -6,16 +6,21 @@ import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.*; +import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import org.example.petshopdesktop.DTOs.ProductDTO; import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.api.dto.common.DropdownOption; import org.example.petshopdesktop.api.dto.product.ProductRequest; +import org.example.petshopdesktop.api.dto.product.ProductResponse; import org.example.petshopdesktop.api.endpoints.DropdownApi; import org.example.petshopdesktop.api.endpoints.ProductApi; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; +import org.example.petshopdesktop.util.FilePickerSupport; +import java.io.File; import java.math.BigDecimal; import java.util.List; @@ -27,6 +32,12 @@ public class ProductDialogController { @FXML private Button btnSave; + @FXML + private Button btnChangeImage; + + @FXML + private Button btnRemoveImage; + @FXML private ComboBox cbProdCategory; @@ -36,6 +47,12 @@ public class ProductDialogController { @FXML private Label lblProdId; + @FXML + private Label lblImageStatus; + + @FXML + private ImageView imgProductPreview; + @FXML private TextField txtProdDesc; @@ -46,6 +63,9 @@ public class ProductDialogController { private TextField txtProdPrice; private String mode = null; + private File selectedImageFile; + private String currentImageUrl; + private boolean removeImageRequested; /** * Add event listeners to buttons when dialog loads @@ -82,6 +102,10 @@ public class ProductDialogController { System.out.println("Error loading categories: " + e.getMessage()); } + btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage()); + btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage()); + refreshImagePreview(); + } /** @@ -123,7 +147,8 @@ public class ProductDialogController { request.setProdDesc(txtProdDesc.getText()); if (mode.equals("Add")) { - ProductApi.getInstance().createProduct(request); + ProductResponse response = ProductApi.getInstance().createProduct(request); + applyImageChanges(response.getProdId()); } else { String[] parts = lblProdId.getText().split(": "); if (parts.length < 2) { @@ -131,6 +156,7 @@ public class ProductDialogController { } Long productId = Long.parseLong(parts[1]); ProductApi.getInstance().updateProduct(productId, request); + applyImageChanges(productId); } Alert alert = new Alert(Alert.AlertType.INFORMATION); @@ -167,6 +193,10 @@ public class ProductDialogController { txtProdName.setText(product.getProdName()); txtProdDesc.setText(product.getProdDesc()); txtProdPrice.setText(product.getProdPrice() + ""); + currentImageUrl = product.getImageUrl(); + selectedImageFile = null; + removeImageRequested = false; + refreshImagePreview(); for (DropdownOption category : cbProdCategory.getItems()) { if(category.getLabel().equals(product.getCategoryName())){ @@ -197,10 +227,67 @@ public class ProductDialogController { lblMode.setText(mode + " Product"); if(mode.equals("Add")) { lblProdId.setVisible(false); + currentImageUrl = null; + selectedImageFile = null; + removeImageRequested = false; + refreshImagePreview(); } else if(mode.equals("Edit")) { lblProdId.setVisible(true); + refreshImagePreview(); } } + private void handleChangeImage() { + File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow()); + if (file == null) { + return; + } + selectedImageFile = file; + removeImageRequested = false; + lblImageStatus.setText("Selected: " + file.getName()); + DesktopImageSupport.loadImageInto(imgProductPreview, file.toURI().toString(), 120, 120); + btnRemoveImage.setDisable(false); + } + + private void handleRemoveImage() { + selectedImageFile = null; + removeImageRequested = true; + currentImageUrl = null; + refreshImagePreview(); + } + + private void applyImageChanges(Long productId) throws Exception { + if (removeImageRequested) { + try { + ProductApi.getInstance().deleteProductImage(productId); + } catch (Exception ignored) { + } + } + if (selectedImageFile != null) { + ProductApi.getInstance().uploadProductImage(productId, selectedImageFile.toPath()); + } + } + + private void refreshImagePreview() { + if (imgProductPreview == null || lblImageStatus == null || btnRemoveImage == null) { + return; + } + imgProductPreview.setImage(null); + if (selectedImageFile != null) { + lblImageStatus.setText("Selected: " + selectedImageFile.getName()); + DesktopImageSupport.loadImageInto(imgProductPreview, selectedImageFile.toURI().toString(), 120, 120); + btnRemoveImage.setDisable(false); + return; + } + if (currentImageUrl != null && !currentImageUrl.isBlank()) { + lblImageStatus.setText("Current image loaded"); + DesktopImageSupport.loadImageInto(imgProductPreview, currentImageUrl, 120, 120); + btnRemoveImage.setDisable(false); + return; + } + lblImageStatus.setText("No image selected"); + btnRemoveImage.setDisable(true); + } + } diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java b/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java index e1f2e3fb..fc1723c5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java @@ -12,8 +12,9 @@ public class Pet { private SimpleIntegerProperty petAge; private SimpleStringProperty petStatus; private SimpleDoubleProperty petPrice; + private SimpleStringProperty imageUrl; - public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice) { + public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice, String imageUrl) { this.petId = new SimpleIntegerProperty(petId); this.petName = new SimpleStringProperty(petName); this.petSpecies = new SimpleStringProperty(petSpecies); @@ -21,6 +22,7 @@ public class Pet { this.petAge = new SimpleIntegerProperty(petAge); this.petStatus = new SimpleStringProperty(petStatus); this.petPrice = new SimpleDoubleProperty(petPrice); + this.imageUrl = new SimpleStringProperty(imageUrl); } public int getPetId() { @@ -106,4 +108,16 @@ public class Pet { public SimpleDoubleProperty petPriceProperty() { return petPrice; } + + public String getImageUrl() { + return imageUrl.get(); + } + + public void setImageUrl(String imageUrl) { + this.imageUrl.set(imageUrl); + } + + public SimpleStringProperty imageUrlProperty() { + return imageUrl; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java new file mode 100644 index 00000000..f4347a1e --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java @@ -0,0 +1,53 @@ +package org.example.petshopdesktop.util; + +import javafx.application.Platform; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import org.example.petshopdesktop.api.ApiClient; + +import java.io.ByteArrayInputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class DesktopImageSupport { + + private static final Map IMAGE_CACHE = new ConcurrentHashMap<>(); + + private DesktopImageSupport() { + } + + public static void loadImageInto(ImageView imageView, String imageUrl, double width, double height) { + imageView.setFitWidth(width); + imageView.setFitHeight(height); + imageView.setPreserveRatio(true); + imageView.setImage(null); + + if (imageUrl == null || imageUrl.isBlank()) { + return; + } + + Image cached = IMAGE_CACHE.get(imageUrl); + if (cached != null) { + imageView.setImage(cached); + return; + } + + new Thread(() -> { + try { + byte[] bytes = ApiClient.getInstance().getBytes(imageUrl); + Image image = new Image(new ByteArrayInputStream(bytes), width, height, true, true); + if (!image.isError()) { + IMAGE_CACHE.put(imageUrl, image); + Platform.runLater(() -> imageView.setImage(image)); + } + } catch (Exception ignored) { + } + }, "desktop-image-loader").start(); + } + + public static void evict(String imageUrl) { + if (imageUrl != null && !imageUrl.isBlank()) { + IMAGE_CACHE.remove(imageUrl); + } + } +} diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml index d4c97ddb..781caeb9 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml @@ -5,6 +5,7 @@ + @@ -163,6 +164,22 @@ + + + + + +