Imrpoved image handling since the size of the image is about 5MB.
This commit is contained in:
@@ -124,12 +124,12 @@ public class CustomerPictureService extends AbstractService {
|
|||||||
return customers.parallelStream().map(CustomerListValue::builder).toList();
|
return customers.parallelStream().map(CustomerListValue::builder).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomerValue get(Long id) {
|
public CustomerValue get(Long id, String baseUrl) {
|
||||||
Customer customer = entityManager.find(Customer.class, id);
|
Customer customer = entityManager.find(Customer.class, id);
|
||||||
if (customer == null) {
|
if (customer == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomerValue.builder(customer);
|
return CustomerValue.builder(customer, baseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package marketing.heyday.hartmann.fotodocumentation.core.service;
|
package marketing.heyday.hartmann.fotodocumentation.core.service;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import jakarta.annotation.security.PermitAll;
|
import jakarta.annotation.security.PermitAll;
|
||||||
import jakarta.ejb.LocalBean;
|
import jakarta.ejb.LocalBean;
|
||||||
import jakarta.ejb.Stateless;
|
import jakarta.ejb.Stateless;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.core.model.Picture;
|
import marketing.heyday.hartmann.fotodocumentation.core.model.Picture;
|
||||||
|
import marketing.heyday.hartmann.fotodocumentation.core.utils.ImageUtil;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.StorageState;
|
import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.StorageState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +29,9 @@ import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.Stora
|
|||||||
public class PictureService extends AbstractService {
|
public class PictureService extends AbstractService {
|
||||||
private static final Log LOG = LogFactory.getLog(PictureService.class);
|
private static final Log LOG = LogFactory.getLog(PictureService.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ImageUtil imageUtil;
|
||||||
|
|
||||||
public StorageState delete(Long id) {
|
public StorageState delete(Long id) {
|
||||||
return super.delete(Picture.class, id);
|
return super.delete(Picture.class, id);
|
||||||
}
|
}
|
||||||
@@ -41,4 +48,17 @@ public class PictureService extends AbstractService {
|
|||||||
return StorageState.NOT_FOUND;
|
return StorageState.NOT_FOUND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getImage(Long id, int size) {
|
||||||
|
try {
|
||||||
|
Picture entity = entityManager.getReference(Picture.class, id);
|
||||||
|
|
||||||
|
String base64 = entity.getImage();
|
||||||
|
return Base64.getDecoder().decode(base64);
|
||||||
|
} catch (EntityNotFoundException e) {
|
||||||
|
LOG.warn("Failed to get image for id " + id, e);
|
||||||
|
ejbContext.setRollbackOnly();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package marketing.heyday.hartmann.fotodocumentation.core.utils;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* <p>Copyright: Copyright (c) 2024</p>
|
||||||
|
* <p>Company: heyday Marketing GmbH</p>
|
||||||
|
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
|
||||||
|
* @version 1.0
|
||||||
|
*
|
||||||
|
* created: 2 Feb 2026
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ImageUtil {
|
||||||
|
private static final Log LOG = LogFactory.getLog(ImageUtil.class);
|
||||||
|
|
||||||
|
private static final int NORMAL_MAX_WIDTH = 1200;
|
||||||
|
private static final float NORMAL_QUALITY = 0.75f;
|
||||||
|
|
||||||
|
private static final int THUMBNAIL_MAX_WIDTH = 200;
|
||||||
|
private static final float THUMBNAIL_QUALITY = 0.6f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* size 1 is original
|
||||||
|
* size 2 is normal (web)
|
||||||
|
* size 3 is thumbnail
|
||||||
|
*/
|
||||||
|
public byte[] getImage(String base64, int size) {
|
||||||
|
byte[] original = Base64.getDecoder().decode(base64);
|
||||||
|
return switch (size) {
|
||||||
|
case 1 -> original;
|
||||||
|
case 2 -> normal(original);
|
||||||
|
case 3 -> thumbnail(original);
|
||||||
|
default -> original;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] normal(byte[] original) {
|
||||||
|
return resize(original, NORMAL_MAX_WIDTH, NORMAL_QUALITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] thumbnail(byte[] original) {
|
||||||
|
return resize(original, THUMBNAIL_MAX_WIDTH, THUMBNAIL_QUALITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] resize(byte[] original, int maxWidth, float quality) {
|
||||||
|
try {
|
||||||
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(original));
|
||||||
|
if (image == null) {
|
||||||
|
LOG.error("Failed to read image from byte array");
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
int originalWidth = image.getWidth();
|
||||||
|
int originalHeight = image.getHeight();
|
||||||
|
|
||||||
|
if (originalWidth <= maxWidth) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
double scale = (double) maxWidth / originalWidth;
|
||||||
|
int targetWidth = maxWidth;
|
||||||
|
int targetHeight = (int) (originalHeight * scale);
|
||||||
|
|
||||||
|
BufferedImage resized = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g2d = resized.createGraphics();
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.drawImage(image, 0, 0, targetWidth, targetHeight, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
return writeJpeg(resized, quality);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to resize image", e);
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] writeJpeg(BufferedImage image, float quality) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
param.setCompressionQuality(quality);
|
||||||
|
|
||||||
|
try (ImageOutputStream ios = ImageIO.createImageOutputStream(output)) {
|
||||||
|
writer.setOutput(ios);
|
||||||
|
writer.write(null, new IIOImage(image, null, null), param);
|
||||||
|
} finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import jakarta.ejb.EJB;
|
import jakarta.ejb.EJB;
|
||||||
import jakarta.enterprise.context.RequestScoped;
|
import jakarta.enterprise.context.RequestScoped;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.Context;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService;
|
import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerListValue;
|
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerListValue;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerValue;
|
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerValue;
|
||||||
@@ -33,6 +35,9 @@ import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerValue;
|
|||||||
public class CustomerResource {
|
public class CustomerResource {
|
||||||
private static final Log LOG = LogFactory.getLog(CustomerResource.class);
|
private static final Log LOG = LogFactory.getLog(CustomerResource.class);
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
@EJB
|
@EJB
|
||||||
private CustomerPictureService customerPictureService;
|
private CustomerPictureService customerPictureService;
|
||||||
|
|
||||||
@@ -56,7 +61,9 @@ public class CustomerResource {
|
|||||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved customer value", content = @Content(mediaType = JSON_OUT, array = @ArraySchema(schema = @Schema(implementation = CustomerValue.class))))
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved customer value", content = @Content(mediaType = JSON_OUT, array = @ArraySchema(schema = @Schema(implementation = CustomerValue.class))))
|
||||||
public Response doGetDetailCustomer(@PathParam("id") Long id) {
|
public Response doGetDetailCustomer(@PathParam("id") Long id) {
|
||||||
LOG.debug("Get Customer details for id " + id);
|
LOG.debug("Get Customer details for id " + id);
|
||||||
var retVal = customerPictureService.get(id);
|
String baseUrl = uriInfo.getBaseUri().toString();
|
||||||
|
|
||||||
|
var retVal = customerPictureService.get(id, baseUrl);
|
||||||
return Response.ok().entity(retVal).build();
|
return Response.ok().entity(retVal).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package marketing.heyday.hartmann.fotodocumentation.rest;
|
package marketing.heyday.hartmann.fotodocumentation.rest;
|
||||||
|
|
||||||
|
import static marketing.heyday.hartmann.fotodocumentation.rest.jackson.ApplicationConfigApi.JSON_OUT;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jboss.resteasy.annotations.GZIP;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
@@ -11,6 +16,7 @@ import jakarta.ws.rs.*;
|
|||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
||||||
import jakarta.ws.rs.core.Response.Status;
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
|
import jakarta.ws.rs.core.StreamingOutput;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.core.service.PictureService;
|
import marketing.heyday.hartmann.fotodocumentation.core.service.PictureService;
|
||||||
import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.StorageState;
|
import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.StorageState;
|
||||||
|
|
||||||
@@ -48,15 +54,35 @@ public class PictureResource {
|
|||||||
@Operation(summary = "Update evaluation for picture data to database")
|
@Operation(summary = "Update evaluation for picture data to database")
|
||||||
@ApiResponse(responseCode = "200", description = "Task successfully updated")
|
@ApiResponse(responseCode = "200", description = "Task successfully updated")
|
||||||
public Response doUpdateEvaluation(@PathParam("id") Long id, @QueryParam("evaluation") Integer value) {
|
public Response doUpdateEvaluation(@PathParam("id") Long id, @QueryParam("evaluation") Integer value) {
|
||||||
if(value == null || value < 1 || value >3) {
|
if (value == null || value < 1 || value > 3) {
|
||||||
return Response.status(Status.BAD_REQUEST).build();
|
return Response.status(Status.BAD_REQUEST).build();
|
||||||
}
|
}
|
||||||
StorageState state = pictureService.updateEvaluationStatus(id, value);
|
StorageState state = pictureService.updateEvaluationStatus(id, value);
|
||||||
return deleteResponse(state).build();
|
return deleteResponse(state).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GZIP
|
||||||
|
@GET
|
||||||
|
@Path("image/{id}")
|
||||||
|
@Produces(JSON_OUT)
|
||||||
|
@Operation(summary = "Get customer value")
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved picture value")
|
||||||
|
public Response doGetPictureImage(@PathParam("id") Long id, @QueryParam("size") int size) {
|
||||||
|
LOG.debug("Get Picture for id " + id + " with size " + size);
|
||||||
|
byte[] retVal = pictureService.getImage(id, size);
|
||||||
|
if (retVal == null) {
|
||||||
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamingOutput streamingOutput = (OutputStream output) -> {
|
||||||
|
LOG.debug("Start writing content to OutputStream available bytes");
|
||||||
|
output.write(retVal);
|
||||||
|
};
|
||||||
|
return Response.status(Status.OK).entity(streamingOutput).build();
|
||||||
|
}
|
||||||
|
|
||||||
protected ResponseBuilder deleteResponse(StorageState state) {
|
protected ResponseBuilder deleteResponse(StorageState state) {
|
||||||
return switch(state) {
|
return switch (state) {
|
||||||
case OK -> Response.status(Status.OK);
|
case OK -> Response.status(Status.OK);
|
||||||
case NOT_FOUND -> Response.status(Status.NOT_FOUND);
|
case NOT_FOUND -> Response.status(Status.NOT_FOUND);
|
||||||
default -> Response.status(Status.INTERNAL_SERVER_ERROR);
|
default -> Response.status(Status.INTERNAL_SERVER_ERROR);
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import marketing.heyday.hartmann.fotodocumentation.core.model.Customer;
|
|||||||
@Schema(name = "Customer")
|
@Schema(name = "Customer")
|
||||||
public record CustomerValue(Long id, String name, String customerNumber, String city, String zip, List<PictureValue> pictures) {
|
public record CustomerValue(Long id, String name, String customerNumber, String city, String zip, List<PictureValue> pictures) {
|
||||||
|
|
||||||
public static CustomerValue builder(Customer customer) {
|
public static CustomerValue builder(Customer customer, String baseUrl) {
|
||||||
if (customer == null) {
|
if (customer == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<PictureValue> pictures = customer.getPictures().parallelStream().map(PictureValue::builder).filter(p -> p != null).toList();
|
List<PictureValue> pictures = customer.getPictures().parallelStream().map(p -> PictureValue.builder(p, baseUrl)).filter(p -> p != null).toList();
|
||||||
return new CustomerValue(customer.getCustomerId(), customer.getName(), customer.getCustomerNumber(), customer.getCity(), customer.getZip(), pictures);
|
return new CustomerValue(customer.getCustomerId(), customer.getName(), customer.getCustomerNumber(), customer.getCity(), customer.getZip(), pictures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,12 +16,15 @@ import marketing.heyday.hartmann.fotodocumentation.core.model.Picture;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@Schema(name = "Picture")
|
@Schema(name = "Picture")
|
||||||
public record PictureValue(Long id, String comment, String category, String image, Date pictureDate, String username, Integer evaluation) {
|
public record PictureValue(Long id, String comment, String category, Date pictureDate, String username, Integer evaluation, String imageUrl, String normalSizeUrl, String thumbnailSizeUrl) {
|
||||||
|
|
||||||
public static PictureValue builder(Picture picture) {
|
public static PictureValue builder(Picture picture, String baseUrl) {
|
||||||
if (picture == null) {
|
if (picture == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new PictureValue(picture.getPictureId(), picture.getComment(), picture.getCategory(), picture.getImage(), picture.getPictureDate(), picture.getUsername(), picture.getEvaluation());
|
String imageUrl = baseUrl + "picture/image/" + picture.getPictureId() + "?size=1";
|
||||||
|
String normalSizeUrl = baseUrl + "picture/image/" + picture.getPictureId() + "?size=2";
|
||||||
|
String thumbnailSizeUrl = baseUrl + "picture/image/" + picture.getPictureId() + "?size=3";
|
||||||
|
return new PictureValue(picture.getPictureId(), picture.getComment(), picture.getCategory(), picture.getPictureDate(), picture.getUsername(), picture.getEvaluation(), imageUrl, normalSizeUrl, thumbnailSizeUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,13 +117,40 @@ public class CustomerPictureResourceTest extends AbstractRestTest {
|
|||||||
assertEquals(401, code);
|
assertEquals(401, code);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void doTest() throws IOException {
|
||||||
|
LOG.info("doAddCustomerPicture");
|
||||||
|
|
||||||
|
//String authorization = getBasicHeader();
|
||||||
|
//LOG.info("authorization: " + authorization);
|
||||||
|
String path = deploymentURL + PATH;
|
||||||
|
Request request = Request.Options(path).addHeader("Accept", "application/json; charset=utf-8");
|
||||||
|
//.addHeader("Authorization", authorization)
|
||||||
|
//.bodyFile(new File(BASE_UPLOAD + "add.json"), ContentType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
HttpResponse httpResponse = executeRequest(request);
|
||||||
|
|
||||||
|
var headers = httpResponse.getAllHeaders();
|
||||||
|
for (var header : headers) {
|
||||||
|
System.out.println(header.getName() + " " + header.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = httpResponse.getStatusLine().getStatusCode();
|
||||||
|
assertEquals(200, code);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
|
||||||
var test = new CustomerPictureResourceTest();
|
var test = new CustomerPictureResourceTest();
|
||||||
|
|
||||||
test.deploymentURL = "http://localhost:8080/";
|
test.deploymentURL = "http://localhost:8080/";
|
||||||
|
test.deploymentURL = "https://hartmann-cue.heydevelop.de/";
|
||||||
test.username = "adm";
|
test.username = "adm";
|
||||||
test.password = "x1t0e7Pb49";
|
test.password = "x1t0e7Pb49";
|
||||||
|
|
||||||
test.doAddCustomerWithPicture();
|
test.doTest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ class PictureDto {
|
|||||||
final int id;
|
final int id;
|
||||||
final String? comment;
|
final String? comment;
|
||||||
final String? category;
|
final String? category;
|
||||||
final String image;
|
|
||||||
int evaluation;
|
int evaluation;
|
||||||
final DateTime pictureDate;
|
final DateTime pictureDate;
|
||||||
final String? username;
|
final String? username;
|
||||||
|
final String imageUrl;
|
||||||
|
final String normalSizeUrl;
|
||||||
|
final String thumbnailSizeUrl;
|
||||||
|
|
||||||
PictureDto(
|
PictureDto(
|
||||||
{required this.id, required this.comment, required this.category, required this.image, required this.evaluation, required this.pictureDate, required this.username});
|
{required this.id, required this.comment, required this.category, required this.evaluation, required this.pictureDate, required this.username, required this.imageUrl, required this.normalSizeUrl, required this.thumbnailSizeUrl});
|
||||||
|
|
||||||
/// Create from JSON response
|
/// Create from JSON response
|
||||||
factory PictureDto.fromJson(Map<String, dynamic> json) {
|
factory PictureDto.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -18,10 +20,12 @@ class PictureDto {
|
|||||||
id: json['id'] as int,
|
id: json['id'] as int,
|
||||||
comment: json['comment'] as String?,
|
comment: json['comment'] as String?,
|
||||||
category: json['category'] as String?,
|
category: json['category'] as String?,
|
||||||
image: json['image'] as String,
|
|
||||||
evaluation: json["evaluation"] as int,
|
evaluation: json["evaluation"] as int,
|
||||||
pictureDate: DateTimeUtils.toDateTime(json['pictureDate']) ?? DateTime.now(),
|
pictureDate: DateTimeUtils.toDateTime(json['pictureDate']) ?? DateTime.now(),
|
||||||
username: json['username'] as String?
|
username: json['username'] as String?,
|
||||||
|
imageUrl: json['imageUrl'] as String,
|
||||||
|
normalSizeUrl: json['normalSizeUrl'] as String,
|
||||||
|
thumbnailSizeUrl: json['thumbnailSizeUrl'] as String,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert' show base64Decode;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fotodocumentation/controller/base_controller.dart';
|
import 'package:fotodocumentation/controller/base_controller.dart';
|
||||||
import 'package:fotodocumentation/controller/customer_controller.dart';
|
import 'package:fotodocumentation/controller/customer_controller.dart';
|
||||||
@@ -218,8 +216,8 @@ class _CustomerWidgetState extends State<CustomerWidget> {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 70, maxHeight: 70),
|
constraints: const BoxConstraints(maxWidth: 70, maxHeight: 70),
|
||||||
child: Image.memory(
|
child: Image.network(
|
||||||
base64Decode(pictureDto.image),
|
pictureDto.thumbnailSizeUrl,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert' show base64Decode;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fotodocumentation/dto/picture_dto.dart';
|
import 'package:fotodocumentation/dto/picture_dto.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
||||||
@@ -18,7 +16,6 @@ class PictureFullscreenDialog extends StatelessWidget {
|
|||||||
final dialogWidth = screenSize.width * 0.9;
|
final dialogWidth = screenSize.width * 0.9;
|
||||||
final dialogHeight = screenSize.height * 0.9 - 50;
|
final dialogHeight = screenSize.height * 0.9 - 50;
|
||||||
|
|
||||||
final imageBytes = base64Decode(dto.image);
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
@@ -52,8 +49,8 @@ class PictureFullscreenDialog extends StatelessWidget {
|
|||||||
scaleEnabled: true,
|
scaleEnabled: true,
|
||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
maxScale: 5.0,
|
maxScale: 5.0,
|
||||||
child: Image.memory(
|
child: Image.network(
|
||||||
imageBytes,
|
dto.imageUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert' show base64Decode;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fotodocumentation/controller/base_controller.dart';
|
import 'package:fotodocumentation/controller/base_controller.dart';
|
||||||
import 'package:fotodocumentation/controller/customer_controller.dart';
|
import 'package:fotodocumentation/controller/customer_controller.dart';
|
||||||
@@ -154,8 +152,8 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minWidth: 100, minHeight: 100),
|
constraints: const BoxConstraints(minWidth: 100, minHeight: 100),
|
||||||
child: Image.memory(
|
child: Image.network(
|
||||||
base64Decode(dto.image),
|
dto.normalSizeUrl,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -350,13 +348,10 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
// Bottom navigation buttons
|
// Bottom navigation buttons
|
||||||
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
|
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
|
||||||
pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate));
|
pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate));
|
||||||
print(pictures);
|
|
||||||
final currentIndex = pictures.indexWhere((p) => p.id == selectedPicture.id);
|
final currentIndex = pictures.indexWhere((p) => p.id == selectedPicture.id);
|
||||||
final hasPrevious = currentIndex > 0;
|
final hasPrevious = currentIndex > 0;
|
||||||
final hasNext = currentIndex < pictures.length - 1;
|
final hasNext = currentIndex < pictures.length - 1;
|
||||||
|
|
||||||
print("hasnext $hasNext hasPrevious $hasPrevious current $currentIndex");
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 24.0),
|
padding: const EdgeInsets.only(bottom: 24.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -435,7 +430,7 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_selectedPicture = pictures[index];
|
_selectedPicture = pictures[index];
|
||||||
});
|
});
|
||||||
}else {
|
} else {
|
||||||
print("empty");
|
print("empty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ void _testDelete(PictureController controller, int response, bool expected) asyn
|
|||||||
|
|
||||||
when(client.delete(Uri.parse('http://localhost:8080/api/picture/4'), headers: {"Accept-Language": "en-US"})).thenAnswer((_) async => http.Response("", response));
|
when(client.delete(Uri.parse('http://localhost:8080/api/picture/4'), headers: {"Accept-Language": "en-US"})).thenAnswer((_) async => http.Response("", response));
|
||||||
|
|
||||||
var dto = await controller.delete(PictureDto(id: 4, image: "", pictureDate: DateTime.now(), category: "", comment: "", evaluation: 1, username: ""));
|
var dto = await controller.delete(PictureDto(id: 4, pictureDate: DateTime.now(), category: "", comment: "", evaluation: 1, username: "", imageUrl: "", normalSizeUrl: "", thumbnailSizeUrl: ""));
|
||||||
expect(dto, expected);
|
expect(dto, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,9 +12,6 @@ import 'package:mockito/mockito.dart';
|
|||||||
import '../testing/test_utils.dart';
|
import '../testing/test_utils.dart';
|
||||||
import '../testing/test_utils.mocks.dart';
|
import '../testing/test_utils.mocks.dart';
|
||||||
|
|
||||||
// Minimal valid base64 encoded 1x1 pixel PNG image
|
|
||||||
const String _testImage = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
DiContainer.instance.initState();
|
DiContainer.instance.initState();
|
||||||
@@ -31,30 +28,36 @@ void main() {
|
|||||||
id: 1,
|
id: 1,
|
||||||
comment: 'First picture comment',
|
comment: 'First picture comment',
|
||||||
category: 'category1',
|
category: 'category1',
|
||||||
image: _testImage,
|
|
||||||
pictureDate: DateTime(2024, 1, 15),
|
pictureDate: DateTime(2024, 1, 15),
|
||||||
username: 'user1',
|
username: 'user1',
|
||||||
evaluation: 1,
|
evaluation: 1,
|
||||||
|
imageUrl: "",
|
||||||
|
normalSizeUrl: "",
|
||||||
|
thumbnailSizeUrl: "",
|
||||||
);
|
);
|
||||||
|
|
||||||
pictureDto2 = PictureDto(
|
pictureDto2 = PictureDto(
|
||||||
id: 2,
|
id: 2,
|
||||||
comment: 'Second picture comment',
|
comment: 'Second picture comment',
|
||||||
category: 'category2',
|
category: 'category2',
|
||||||
image: _testImage,
|
|
||||||
pictureDate: DateTime(2024, 2, 20),
|
pictureDate: DateTime(2024, 2, 20),
|
||||||
username: 'user2',
|
username: 'user2',
|
||||||
evaluation: 1,
|
evaluation: 1,
|
||||||
|
imageUrl: "",
|
||||||
|
normalSizeUrl: "",
|
||||||
|
thumbnailSizeUrl: "",
|
||||||
);
|
);
|
||||||
|
|
||||||
pictureDto3 = PictureDto(
|
pictureDto3 = PictureDto(
|
||||||
id: 3,
|
id: 3,
|
||||||
comment: null,
|
comment: null,
|
||||||
category: 'category3',
|
category: 'category3',
|
||||||
image: _testImage,
|
|
||||||
pictureDate: DateTime(2024, 3, 25),
|
pictureDate: DateTime(2024, 3, 25),
|
||||||
username: 'user3',
|
username: 'user3',
|
||||||
evaluation: 1,
|
evaluation: 1,
|
||||||
|
imageUrl: "",
|
||||||
|
normalSizeUrl: "",
|
||||||
|
thumbnailSizeUrl: "",
|
||||||
);
|
);
|
||||||
|
|
||||||
customerDto = CustomerDto(
|
customerDto = CustomerDto(
|
||||||
@@ -170,7 +173,6 @@ void main() {
|
|||||||
expect(find.text('First picture comment'), findsOneWidget);
|
expect(find.text('First picture comment'), findsOneWidget);
|
||||||
|
|
||||||
// Tap right navigation button
|
// Tap right navigation button
|
||||||
print(find.byKey(Key("navigate_right")));
|
|
||||||
await tester.tap(find.byKey(Key("navigate_right")));
|
await tester.tap(find.byKey(Key("navigate_right")));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user