rework ui
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
package marketing.heyday.hartmann.fotodocumentation.core.service;
|
package marketing.heyday.hartmann.fotodocumentation.core.service;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
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.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.StorageUtils.StorageState;
|
import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.StorageState;
|
||||||
|
|
||||||
@@ -19,8 +23,22 @@ import marketing.heyday.hartmann.fotodocumentation.core.utils.StorageUtils.Stora
|
|||||||
@LocalBean
|
@LocalBean
|
||||||
@PermitAll
|
@PermitAll
|
||||||
public class PictureService extends AbstractService {
|
public class PictureService extends AbstractService {
|
||||||
|
private static final Log LOG = LogFactory.getLog(PictureService.class);
|
||||||
|
|
||||||
public StorageState delete(Long id) {
|
public StorageState delete(Long id) {
|
||||||
return super.delete(Picture.class, id);
|
return super.delete(Picture.class, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StorageState updateEvaluationStatus(Long id, Integer value) {
|
||||||
|
try {
|
||||||
|
Picture entity = entityManager.getReference(Picture.class, id);
|
||||||
|
entity.setEvaluation(value);
|
||||||
|
entityManager.flush();
|
||||||
|
return StorageState.OK;
|
||||||
|
} catch (EntityNotFoundException e) {
|
||||||
|
LOG.warn("Failed to update evaluation value not found " + id, e);
|
||||||
|
ejbContext.setRollbackOnly();
|
||||||
|
return StorageState.NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
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.DELETE;
|
import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.PathParam;
|
|
||||||
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;
|
||||||
@@ -45,6 +43,18 @@ public class PictureResource {
|
|||||||
return deleteResponse(state).build();
|
return deleteResponse(state).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("evaluation/{id}")
|
||||||
|
@Operation(summary = "Update evaluation for picture data to database")
|
||||||
|
@ApiResponse(responseCode = "200", description = "Task successfully updated")
|
||||||
|
public Response doUpdateTask(@PathParam("id") Long id, @QueryParam("evaluation") Integer value) {
|
||||||
|
if(value == null || value < 1 || value >3) {
|
||||||
|
return Response.status(Status.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
StorageState state = pictureService.updateEvaluationStatus(id, value);
|
||||||
|
return deleteResponse(state).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);
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ abstract class BaseController {
|
|||||||
var response = await client.delete(uri, headers: {cred.name: cred.value});
|
var response = await client.delete(uri, headers: {cred.name: cred.value});
|
||||||
return response.statusCode == 200;
|
return response.statusCode == 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> runPutWithAuth(String uriStr) async {
|
||||||
|
http.Client client = httpClientUtils.client;
|
||||||
|
Header cred = await getAuthHeader();
|
||||||
|
Uri uri = Uri.parse(uriStr);
|
||||||
|
var response = await client.put(uri, headers: {cred.name: cred.value});
|
||||||
|
return response.statusCode == 200;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Header {
|
class Header {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:fotodocumentation/dto/picture_dto.dart';
|
|||||||
|
|
||||||
abstract interface class PictureController {
|
abstract interface class PictureController {
|
||||||
Future<bool> delete(PictureDto dto);
|
Future<bool> delete(PictureDto dto);
|
||||||
|
Future<bool> updateEvaluation(PictureDto dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PictureControllerImpl extends BaseController implements PictureController {
|
class PictureControllerImpl extends BaseController implements PictureController {
|
||||||
@@ -13,4 +14,10 @@ class PictureControllerImpl extends BaseController implements PictureController
|
|||||||
String uriStr = '${uriUtils.getBaseUrl()}$path/${dto.id}';
|
String uriStr = '${uriUtils.getBaseUrl()}$path/${dto.id}';
|
||||||
return runDeleteWithAuth(uriStr);
|
return runDeleteWithAuth(uriStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> updateEvaluation(PictureDto dto) {
|
||||||
|
String uriStr = '${uriUtils.getBaseUrl()}$path/evaluation/${dto.id}?evaluation=${dto.evaluation}';
|
||||||
|
return runPutWithAuth(uriStr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class PictureDto {
|
|||||||
final String? comment;
|
final String? comment;
|
||||||
final String? category;
|
final String? category;
|
||||||
final String image;
|
final String image;
|
||||||
final int evaluation;
|
int evaluation;
|
||||||
final DateTime pictureDate;
|
final DateTime pictureDate;
|
||||||
final String? username;
|
final String? username;
|
||||||
|
|
||||||
|
|||||||
@@ -60,15 +60,15 @@
|
|||||||
"@deleteDialogTitle": {
|
"@deleteDialogTitle": {
|
||||||
"description": "Delete dialog title"
|
"description": "Delete dialog title"
|
||||||
},
|
},
|
||||||
"deleteDialogText": "Sind Sie sicher, dass Sie diese Eintrag löschen möchten?",
|
"deleteDialogText": "Sicher, dass Sie den Eintrag löschen möchten?",
|
||||||
"@deleteDialogText": {
|
"@deleteDialogText": {
|
||||||
"description": "Delete dialog text"
|
"description": "Delete dialog text"
|
||||||
},
|
},
|
||||||
"deleteDialogButtonCancel": "Nein",
|
"deleteDialogButtonCancel": "Abbrechen",
|
||||||
"@deleteDialogButtonCancel": {
|
"@deleteDialogButtonCancel": {
|
||||||
"description": "Cancel Button text"
|
"description": "Cancel Button text"
|
||||||
},
|
},
|
||||||
"deleteDialogButtonApprove": "Ja",
|
"deleteDialogButtonApprove": "Ja, fortfahren",
|
||||||
"@deleteDialogButtonApprove": {
|
"@deleteDialogButtonApprove": {
|
||||||
"description": "Approve Button text"
|
"description": "Approve Button text"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -169,19 +169,19 @@ abstract class AppLocalizations {
|
|||||||
/// Delete dialog text
|
/// Delete dialog text
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Sind Sie sicher, dass Sie diese Eintrag löschen möchten?'**
|
/// **'Sicher, dass Sie den Eintrag löschen möchten?'**
|
||||||
String get deleteDialogText;
|
String get deleteDialogText;
|
||||||
|
|
||||||
/// Cancel Button text
|
/// Cancel Button text
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Nein'**
|
/// **'Abbrechen'**
|
||||||
String get deleteDialogButtonCancel;
|
String get deleteDialogButtonCancel;
|
||||||
|
|
||||||
/// Approve Button text
|
/// Approve Button text
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Ja'**
|
/// **'Ja, fortfahren'**
|
||||||
String get deleteDialogButtonApprove;
|
String get deleteDialogButtonApprove;
|
||||||
|
|
||||||
/// Customer list table header for customer number
|
/// Customer list table header for customer number
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteDialogText =>
|
String get deleteDialogText =>
|
||||||
'Sind Sie sicher, dass Sie diese Eintrag löschen möchten?';
|
'Sicher, dass Sie den Eintrag löschen möchten?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteDialogButtonCancel => 'Nein';
|
String get deleteDialogButtonCancel => 'Abbrechen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteDialogButtonApprove => 'Ja';
|
String get deleteDialogButtonApprove => 'Ja, fortfahren';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get customerListHeaderCustomerNumber => 'Kunden-Nr.';
|
String get customerListHeaderCustomerNumber => 'Kunden-Nr.';
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ class _CustomerListWidgetState extends State<CustomerListWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _actionSelect(BuildContext context, CustomerListDto dto) async {
|
Future<void> _actionSelect(BuildContext context, CustomerListDto dto) async {
|
||||||
context.push("${GlobalRouter.pathCustomer}/${dto.id}");
|
String uri = "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/${dto.id}";
|
||||||
|
context.go(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reloadData() {
|
void _reloadData() {
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
import 'dart:convert' show base64Decode;
|
import 'dart:convert' show base64Decode;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fotodocumentation/controller/picture_controller.dart';
|
|
||||||
import 'package:fotodocumentation/dto/picture_dto.dart';
|
|
||||||
import 'package:fotodocumentation/pages/customer/back_button.dart';
|
|
||||||
import 'package:fotodocumentation/pages/customer/picture_delete_dialog.dart';
|
|
||||||
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
import 'package:fotodocumentation/controller/base_controller.dart';
|
import 'package:fotodocumentation/controller/base_controller.dart';
|
||||||
import 'package:fotodocumentation/l10n/app_localizations.dart';
|
|
||||||
import 'package:fotodocumentation/controller/customer_controller.dart';
|
import 'package:fotodocumentation/controller/customer_controller.dart';
|
||||||
|
import 'package:fotodocumentation/controller/picture_controller.dart';
|
||||||
import 'package:fotodocumentation/dto/customer_dto.dart';
|
import 'package:fotodocumentation/dto/customer_dto.dart';
|
||||||
|
import 'package:fotodocumentation/dto/picture_dto.dart';
|
||||||
|
import 'package:fotodocumentation/l10n/app_localizations.dart';
|
||||||
|
import 'package:fotodocumentation/pages/ui_utils/component/customer_back_button.dart';
|
||||||
|
import 'package:fotodocumentation/pages/customer/picture_delete_dialog.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart';
|
import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart';
|
import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart';
|
import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
||||||
import 'package:fotodocumentation/utils/di_container.dart';
|
import 'package:fotodocumentation/utils/di_container.dart';
|
||||||
import 'package:fotodocumentation/utils/global_router.dart';
|
import 'package:fotodocumentation/utils/global_router.dart';
|
||||||
import 'package:fotodocumentation/pages/customer/picture_widget.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class CustomerWidget extends StatefulWidget {
|
class CustomerWidget extends StatefulWidget {
|
||||||
final int customerId;
|
final int customerId;
|
||||||
@@ -98,7 +94,6 @@ class _CustomerWidgetState extends State<CustomerWidget> {
|
|||||||
children: [
|
children: [
|
||||||
PageHeaderWidget(text: dto.name, subText: subText),
|
PageHeaderWidget(text: dto.name, subText: subText),
|
||||||
CustomerBackButton(path: GlobalRouter.pathHome),
|
CustomerBackButton(path: GlobalRouter.pathHome),
|
||||||
//_backButton(context),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
@@ -209,7 +204,7 @@ class _CustomerWidgetState extends State<CustomerWidget> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final dateStr = _dateFormat.format(pictureDto.pictureDate);
|
final dateStr = _dateFormat.format(pictureDto.pictureDate);
|
||||||
final evaluationColor = _generalStyle.evaluationColor(); // FIXME: set to color from DB
|
final evaluationColor = _generalStyle.evaluationColor(value: pictureDto.evaluation); // FIXME: set to color from DB
|
||||||
return InkWell(
|
return InkWell(
|
||||||
key: Key("table_row_${customerDto.id}"),
|
key: Key("table_row_${customerDto.id}"),
|
||||||
onTap: () => _actionSelect(context, customerDto, pictureDto),
|
onTap: () => _actionSelect(context, customerDto, pictureDto),
|
||||||
@@ -288,33 +283,6 @@ class _CustomerWidgetState extends State<CustomerWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _backButton(BuildContext context) {
|
|
||||||
return ElevatedButton.icon(
|
|
||||||
onPressed: () => context.push(GlobalRouter.pathHome),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.chevron_left,
|
|
||||||
color: _generalStyle.secondaryTextLabelColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
label: Text(
|
|
||||||
AppLocalizations.of(context)!.backButtonLabel,
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: _generalStyle.fontFamily,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: 16,
|
|
||||||
color: _generalStyle.secondaryTextLabelColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _downloadButton(BuildContext context) {
|
Widget _downloadButton(BuildContext context) {
|
||||||
return ElevatedButton.icon(
|
return ElevatedButton.icon(
|
||||||
key: Key("download_all_button"),
|
key: Key("download_all_button"),
|
||||||
@@ -360,7 +328,11 @@ class _CustomerWidgetState extends State<CustomerWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _actionSelect(BuildContext context, CustomerDto customerDto, PictureDto pictureDto) async {
|
Future<void> _actionSelect(BuildContext context, CustomerDto customerDto, PictureDto pictureDto) async {
|
||||||
context.go(GlobalRouter.pathPicture, extra: PictureWidgetHolder(customerDto, pictureDto));
|
String uri = "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/${customerDto.id}/${pictureDto.id}";
|
||||||
|
context.go(uri);
|
||||||
|
setState(() {
|
||||||
|
_dto = _customerController.get(id: widget.customerId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _actionDownload(BuildContext context) async {
|
Future<void> _actionDownload(BuildContext context) async {
|
||||||
|
|||||||
@@ -11,41 +11,77 @@ class PictureDeleteDialog extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
AppLocalizations.of(context)!.deleteDialogTitle,
|
actionsPadding: EdgeInsets.only(bottom: 50),
|
||||||
style: TextStyle(
|
content: Column(
|
||||||
fontFamily: _generalStyle.fontFamily,
|
mainAxisSize: MainAxisSize.min,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
color: _generalStyle.secondaryWidgetBackgroundColor,
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 50.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _generalStyle.errorColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
size: 32,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: Text(
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left:50, right: 50, bottom: 50),
|
||||||
|
child: Text(
|
||||||
AppLocalizations.of(context)!.deleteDialogText,
|
AppLocalizations.of(context)!.deleteDialogText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: _generalStyle.fontFamily,
|
fontFamily: _generalStyle.fontFamily,
|
||||||
color: _generalStyle.secondaryTextLabelColor,
|
color: _generalStyle.secondaryTextLabelColor,
|
||||||
|
fontSize: 28.71,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
ElevatedButton(
|
||||||
key: Key("picture_delete_no"),
|
key: Key("picture_delete_no"),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _generalStyle.deleteCancelButtonBackgroundColor,
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.deleteDialogButtonCancel,
|
AppLocalizations.of(context)!.deleteDialogButtonCancel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: _generalStyle.fontFamily,
|
fontFamily: _generalStyle.fontFamily,
|
||||||
color: _generalStyle.secondaryTextLabelColor,
|
color: _generalStyle.deleteCancelTextColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18.37,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
ElevatedButton(
|
||||||
key: Key("picture_delete_yes"),
|
key: Key("picture_delete_yes"),
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _generalStyle.primaryButtonBackgroundColor,
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.deleteDialogButtonApprove,
|
AppLocalizations.of(context)!.deleteDialogButtonApprove,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: _generalStyle.fontFamily,
|
fontFamily: _generalStyle.fontFamily,
|
||||||
color: _generalStyle.errorColor,
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18.37,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
import 'dart:convert' show base64Decode;
|
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/customer_controller.dart';
|
||||||
|
import 'package:fotodocumentation/controller/picture_controller.dart';
|
||||||
import 'package:fotodocumentation/dto/customer_dto.dart';
|
import 'package:fotodocumentation/dto/customer_dto.dart';
|
||||||
import 'package:fotodocumentation/dto/picture_dto.dart';
|
import 'package:fotodocumentation/dto/picture_dto.dart';
|
||||||
import 'package:fotodocumentation/pages/customer/customer_back_button.dart';
|
import 'package:fotodocumentation/pages/ui_utils/component/customer_back_button.dart';
|
||||||
import 'package:fotodocumentation/pages/customer/picture_fullscreen_dialog.dart';
|
import 'package:fotodocumentation/pages/customer/picture_fullscreen_dialog.dart';
|
||||||
|
import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart';
|
import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart';
|
||||||
|
import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart';
|
||||||
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
|
||||||
import 'package:fotodocumentation/utils/di_container.dart';
|
import 'package:fotodocumentation/utils/di_container.dart';
|
||||||
import 'package:fotodocumentation/utils/global_router.dart';
|
import 'package:fotodocumentation/utils/global_router.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class PictureWidgetHolder {
|
|
||||||
final CustomerDto customerDto;
|
|
||||||
final PictureDto pictureDto;
|
|
||||||
|
|
||||||
const PictureWidgetHolder(this.customerDto, this.pictureDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PictureWidget extends StatefulWidget {
|
class PictureWidget extends StatefulWidget {
|
||||||
final CustomerDto customerDto;
|
final int customerId;
|
||||||
final PictureDto pictureDto;
|
final int pictureId;
|
||||||
const PictureWidget({super.key, required this.customerDto, required this.pictureDto});
|
const PictureWidget({super.key, required this.customerId, required this.pictureId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PictureWidget> createState() => _PictureWidgetState();
|
State<PictureWidget> createState() => _PictureWidgetState();
|
||||||
@@ -29,16 +27,21 @@ class PictureWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _PictureWidgetState extends State<PictureWidget> {
|
class _PictureWidgetState extends State<PictureWidget> {
|
||||||
GeneralStyle get _generalStyle => DiContainer.get();
|
GeneralStyle get _generalStyle => DiContainer.get();
|
||||||
|
CustomerController get _customerController => DiContainer.get();
|
||||||
|
PictureController get _pictureController => DiContainer.get();
|
||||||
|
|
||||||
late PictureDto _selectedPicture;
|
late CustomerDto _customerDto;
|
||||||
|
PictureDto? _selectedPicture;
|
||||||
late DateFormat _dateFormat;
|
late DateFormat _dateFormat;
|
||||||
final ScrollController _commentScrollController = ScrollController();
|
final ScrollController _commentScrollController = ScrollController();
|
||||||
|
|
||||||
|
late Future<CustomerDto?> _dto;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_dateFormat = DateFormat('dd.MM.yyyy, hh:mm');
|
_dateFormat = DateFormat('dd.MM.yyyy, hh:mm');
|
||||||
_selectedPicture = widget.pictureDto;
|
_dto = _customerController.get(id: widget.customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -54,30 +57,58 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
color: _generalStyle.pageBackgroundColor,
|
color: _generalStyle.pageBackgroundColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0, left: 50.0, right: 50.0, bottom: 8.0),
|
padding: const EdgeInsets.only(top: 8.0, left: 50.0, right: 50.0, bottom: 8.0),
|
||||||
child: _body(context),
|
child: _future(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _body(BuildContext context) {
|
Widget _future(BuildContext context) {
|
||||||
final pictures = widget.customerDto.pictures;
|
return FutureBuilder<CustomerDto?>(
|
||||||
|
future: _dto,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<CustomerDto?> snapshot) {
|
||||||
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
|
return const WaitingWidget();
|
||||||
|
}
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
CustomerDto? dto = snapshot.data;
|
||||||
|
if (dto == null) {
|
||||||
|
return GeneralErrorWidget(error: "FIXME"); // FIXME: set error text data not found
|
||||||
|
}
|
||||||
|
_customerDto = dto;
|
||||||
|
_selectedPicture ??= dto.pictures.firstWhere((p) => p.id == widget.pictureId);
|
||||||
|
_selectedPicture ??= _customerDto.pictures.firstOrNull;
|
||||||
|
if (_selectedPicture == null) {
|
||||||
|
return GeneralErrorWidget(error: "FIXME"); // FIXME: set error text data not found
|
||||||
|
}
|
||||||
|
return _body(context, _selectedPicture!);
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
var error = snapshot.error;
|
||||||
|
return (error is ServerError) ? GeneralErrorWidget.fromServerError(error) : GeneralErrorWidget(error: snapshot.error.toString());
|
||||||
|
}
|
||||||
|
return const WaitingWidget();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _body(BuildContext context, PictureDto selectedPicture) {
|
||||||
|
final pictures = _customerDto.pictures;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
PageHeaderWidget(text: widget.customerDto.name),
|
PageHeaderWidget(text: _customerDto.name),
|
||||||
CustomerBackButton(path: GlobalRouter.pathCustomer),
|
CustomerBackButton(path: GlobalRouter.pathCustomer),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _mainWidget(context),
|
child: _mainWidget(context, selectedPicture),
|
||||||
),
|
),
|
||||||
_bottomNavigationWidget(pictures),
|
_bottomNavigationWidget(pictures, selectedPicture),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _mainWidget(BuildContext context) {
|
Widget _mainWidget(BuildContext context, PictureDto selectedPicture) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isNarrow = constraints.maxWidth < 800;
|
final isNarrow = constraints.maxWidth < 800;
|
||||||
@@ -87,18 +118,18 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_imageWidget(_selectedPicture),
|
_imageWidget(selectedPicture),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
_contentWidget(_selectedPicture),
|
_contentWidget(selectedPicture),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Row(
|
: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_imageWidget(_selectedPicture),
|
_imageWidget(selectedPicture),
|
||||||
const SizedBox(width: 32),
|
const SizedBox(width: 32),
|
||||||
Expanded(child: _contentWidget(_selectedPicture)),
|
Expanded(child: _contentWidget(selectedPicture)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -148,7 +179,9 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
color: _generalStyle.secondaryTextLabelColor,
|
color: _generalStyle.secondaryTextLabelColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_dateFormat.format(dto.pictureDate),
|
_dateFormat.format(dto.pictureDate),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -158,18 +191,22 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
color: _generalStyle.primaryTextLabelColor,
|
color: _generalStyle.primaryTextLabelColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 20.0),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Text(
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
"KUNDENNUMMER",
|
"KUNDENNUMMER",
|
||||||
style: labelStyle,
|
style: labelStyle,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 4.0),
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.customerDto.customerNumber,
|
_customerDto.customerNumber,
|
||||||
style: contentStyle,
|
style: contentStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -199,12 +236,93 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
_evaluationCard(dto),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _evaluationCard(PictureDto dto) {
|
||||||
|
return Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"BEWERTUNG",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: _generalStyle.fontFamily,
|
||||||
|
color: _generalStyle.primaryTextLabelColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_evaluationCircle(
|
||||||
|
key: const Key("evaluation_good"),
|
||||||
|
color: _generalStyle.evaluationGoodColor,
|
||||||
|
value: 1,
|
||||||
|
selected: dto.evaluation == 1,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_evaluationCircle(
|
||||||
|
key: const Key("evaluation_middle"),
|
||||||
|
color: _generalStyle.evaluationMiddleColor,
|
||||||
|
value: 2,
|
||||||
|
selected: dto.evaluation == 2,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_evaluationCircle(
|
||||||
|
key: const Key("evaluation_bad"),
|
||||||
|
color: _generalStyle.evaluationBadColor,
|
||||||
|
value: 3,
|
||||||
|
selected: dto.evaluation == 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _evaluationCircle({
|
||||||
|
required Key key,
|
||||||
|
required Color color,
|
||||||
|
required int value,
|
||||||
|
required bool selected,
|
||||||
|
}) {
|
||||||
|
return InkWell(
|
||||||
|
key: key,
|
||||||
|
onTap: () async => _actionUpdateEvaluation(value),
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
child: Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: selected ? Border.all(color: _generalStyle.secondaryWidgetBackgroundColor, width: 3) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom navigation buttons
|
// Bottom navigation buttons
|
||||||
Widget _bottomNavigationWidget(List<PictureDto> pictures) {
|
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
|
||||||
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;
|
||||||
|
|
||||||
@@ -262,8 +380,16 @@ class _PictureWidgetState extends State<PictureWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _actionUpdateEvaluation(int value) async {
|
||||||
|
_selectedPicture?.evaluation = value;
|
||||||
|
_pictureController.updateEvaluation(_selectedPicture!);
|
||||||
|
setState(() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _actionNavigateToPicture(int index) {
|
void _actionNavigateToPicture(int index) {
|
||||||
final pictures = widget.customerDto.pictures;
|
final pictures = _customerDto.pictures;
|
||||||
if (index >= 0 && index < pictures.length) {
|
if (index >= 0 && index < pictures.length) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPicture = pictures[index];
|
_selectedPicture = pictures[index];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class CustomerBackButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ElevatedButton.icon(
|
return ElevatedButton.icon(
|
||||||
onPressed: () => context.go(path),
|
onPressed: () => context.pop(),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.chevron_left,
|
Icons.chevron_left,
|
||||||
color: _generalStyle.secondaryTextLabelColor,
|
color: _generalStyle.secondaryTextLabelColor,
|
||||||
@@ -20,11 +20,24 @@ class PageHeaderWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 24.0, bottom: 24.0),
|
padding: const EdgeInsets.only(top: 24.0, bottom: 24.0),
|
||||||
child: Column(
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final isNarrow = constraints.maxWidth < 600;
|
||||||
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
if (isNarrow) ...[
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
_logoutButton(context),
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/logo.png',
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
key: Key("PageHeaderTextHeadline"),
|
key: Key("PageHeaderTextHeadline"),
|
||||||
text,
|
text,
|
||||||
@@ -35,7 +48,21 @@ class PageHeaderWidget extends StatelessWidget {
|
|||||||
color: _generalStyle.primaryTextLabelColor,
|
color: _generalStyle.primaryTextLabelColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
] else ...[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
key: Key("PageHeaderTextHeadline"),
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 50,
|
||||||
|
fontFamily: _generalStyle.fontFamily,
|
||||||
|
color: _generalStyle.primaryTextLabelColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
_logoutButton(context),
|
_logoutButton(context),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
'assets/images/logo.png',
|
'assets/images/logo.png',
|
||||||
@@ -43,6 +70,7 @@ class PageHeaderWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
if (subText.isNotEmpty) ...[
|
if (subText.isNotEmpty) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
@@ -56,6 +84,8 @@ class PageHeaderWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
abstract interface class GeneralStyle {
|
abstract interface class GeneralStyle {
|
||||||
Color? evaluationColor({int value = 0});
|
Color? evaluationColor({required int value});
|
||||||
|
|
||||||
Color get evaluationGoodColor;
|
Color get evaluationGoodColor;
|
||||||
Color get evaluationMiddleColor;
|
Color get evaluationMiddleColor;
|
||||||
@@ -20,6 +20,8 @@ abstract interface class GeneralStyle {
|
|||||||
Color get secondaryWidgetBackgroundColor;
|
Color get secondaryWidgetBackgroundColor;
|
||||||
|
|
||||||
Color get pageBackgroundColor;
|
Color get pageBackgroundColor;
|
||||||
|
Color get deleteCancelButtonBackgroundColor;
|
||||||
|
Color get deleteCancelTextColor;
|
||||||
|
|
||||||
Color get errorColor;
|
Color get errorColor;
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ abstract interface class GeneralStyle {
|
|||||||
|
|
||||||
class GeneralStyleImpl implements GeneralStyle {
|
class GeneralStyleImpl implements GeneralStyle {
|
||||||
@override
|
@override
|
||||||
Color? evaluationColor({int value = 0}) {
|
Color? evaluationColor({required int value}) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 1:
|
case 1:
|
||||||
return evaluationGoodColor;
|
return evaluationGoodColor;
|
||||||
@@ -70,6 +72,12 @@ class GeneralStyleImpl implements GeneralStyle {
|
|||||||
@override
|
@override
|
||||||
Color get pageBackgroundColor => const Color(0xFFF5F5F5);
|
Color get pageBackgroundColor => const Color(0xFFF5F5F5);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get deleteCancelButtonBackgroundColor => const Color(0xFFD9D9D9);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get deleteCancelTextColor => const Color(0xFF1E1E1E);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get nextTextColor => const Color(0xFF757575);
|
Color get nextTextColor => const Color(0xFF757575);
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class GlobalRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: pathHome,
|
path: pathHome,
|
||||||
builder: (context, state) => CustomerListWidget(),
|
builder: (context, state) => CustomerListWidget(),
|
||||||
),
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "$pathCustomer/:id",
|
path: "$pathCustomer/:id",
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -48,13 +48,19 @@ class GlobalRouter {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: pathPicture,
|
path: "$pathPicture/:customerId/:pictureId",
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
PictureWidgetHolder holder = state.extra as PictureWidgetHolder;
|
var customerIdStr = state.pathParameters['customerId'];
|
||||||
return PictureWidget(customerDto: holder.customerDto, pictureDto: holder.pictureDto);
|
var customerId = customerIdStr == null ? null : int.tryParse(customerIdStr);
|
||||||
|
|
||||||
|
var pictureIdStr = state.pathParameters['pictureId'];
|
||||||
|
var pictureId = pictureIdStr == null ? null : int.tryParse(pictureIdStr);
|
||||||
|
return PictureWidget(customerId: customerId ?? -1, pictureId: pictureId ?? -1);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
redirect: (context, state) {
|
redirect: (context, state) {
|
||||||
var uriStr = state.uri.toString();
|
var uriStr = state.uri.toString();
|
||||||
logger.t("uri $uriStr");
|
logger.t("uri $uriStr");
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,13 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:fotodocumentation/controller/customer_controller.dart';
|
||||||
import 'package:fotodocumentation/dto/customer_dto.dart' show CustomerDto;
|
import 'package:fotodocumentation/dto/customer_dto.dart' show CustomerDto;
|
||||||
import 'package:fotodocumentation/dto/picture_dto.dart';
|
import 'package:fotodocumentation/dto/picture_dto.dart';
|
||||||
import 'package:fotodocumentation/pages/customer/picture_widget.dart';
|
|
||||||
import 'package:fotodocumentation/pages/customer/picture_fullscreen_dialog.dart';
|
import 'package:fotodocumentation/pages/customer/picture_fullscreen_dialog.dart';
|
||||||
import 'package:fotodocumentation/utils/di_container.dart';
|
import 'package:fotodocumentation/utils/di_container.dart';
|
||||||
|
import 'package:fotodocumentation/utils/global_router.dart';
|
||||||
import 'package:fotodocumentation/utils/login_credentials.dart';
|
import 'package:fotodocumentation/utils/login_credentials.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import '../testing/test_utils.dart';
|
import '../testing/test_utils.dart';
|
||||||
|
import '../testing/test_utils.mocks.dart';
|
||||||
|
|
||||||
// Minimal valid base64 encoded 1x1 pixel PNG image
|
// Minimal valid base64 encoded 1x1 pixel PNG image
|
||||||
const String _testImage =
|
const String _testImage =
|
||||||
@@ -22,6 +25,7 @@ void main() {
|
|||||||
late PictureDto pictureDto1;
|
late PictureDto pictureDto1;
|
||||||
late PictureDto pictureDto2;
|
late PictureDto pictureDto2;
|
||||||
late PictureDto pictureDto3;
|
late PictureDto pictureDto3;
|
||||||
|
late MockCustomerController mockCustomerController;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
pictureDto1 = PictureDto(
|
pictureDto1 = PictureDto(
|
||||||
@@ -60,20 +64,19 @@ void main() {
|
|||||||
customerNumber: 'CUST001',
|
customerNumber: 'CUST001',
|
||||||
pictures: [pictureDto1, pictureDto2, pictureDto3],
|
pictures: [pictureDto1, pictureDto2, pictureDto3],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mockCustomerController = MockCustomerController();
|
||||||
|
DiContainer.instance.put(CustomerController, mockCustomerController);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('PictureWidget', () {
|
group('PictureWidget', () {
|
||||||
testWidgets('displays customer information correctly', (WidgetTester tester) async {
|
testWidgets('displays customer information correctly', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto1),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Verify that the header is displayed
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
expect(find.text('INFORMATIONEN'), findsOneWidget);
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Verify customer name is displayed
|
// Verify customer name is displayed
|
||||||
expect(find.text('Test Apotheke'), findsOneWidget);
|
expect(find.text('Test Apotheke'), findsOneWidget);
|
||||||
@@ -82,19 +85,16 @@ void main() {
|
|||||||
expect(find.text('CUST001'), findsOneWidget);
|
expect(find.text('CUST001'), findsOneWidget);
|
||||||
|
|
||||||
// Verify labels are displayed
|
// Verify labels are displayed
|
||||||
expect(find.text('APOTHEKE'), findsOneWidget);
|
|
||||||
expect(find.text('KUNDENNUMMER'), findsOneWidget);
|
expect(find.text('KUNDENNUMMER'), findsOneWidget);
|
||||||
expect(find.text('DATUM'), findsOneWidget);
|
|
||||||
expect(find.text('KOMMENTAR'), findsOneWidget);
|
expect(find.text('KOMMENTAR'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays picture comment', (WidgetTester tester) async {
|
testWidgets('displays picture comment', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto1),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Verify the comment is displayed
|
// Verify the comment is displayed
|
||||||
@@ -104,10 +104,9 @@ void main() {
|
|||||||
testWidgets('displays empty string when comment is null', (WidgetTester tester) async {
|
testWidgets('displays empty string when comment is null', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto3),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/3");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// The comment field should be empty but the label should exist
|
// The comment field should be empty but the label should exist
|
||||||
@@ -117,10 +116,9 @@ void main() {
|
|||||||
testWidgets('shows both navigation buttons at bottom', (WidgetTester tester) async {
|
testWidgets('shows both navigation buttons at bottom', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto1),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Both navigation buttons should always be visible at the bottom
|
// Both navigation buttons should always be visible at the bottom
|
||||||
@@ -131,10 +129,9 @@ void main() {
|
|||||||
testWidgets('shows both navigation buttons when on middle picture', (WidgetTester tester) async {
|
testWidgets('shows both navigation buttons when on middle picture', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto2),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/2");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Both navigation buttons should be visible
|
// Both navigation buttons should be visible
|
||||||
@@ -145,10 +142,9 @@ void main() {
|
|||||||
testWidgets('shows both navigation buttons on last picture', (WidgetTester tester) async {
|
testWidgets('shows both navigation buttons on last picture', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto3),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/3");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Both navigation buttons should be visible (right one is disabled)
|
// Both navigation buttons should be visible (right one is disabled)
|
||||||
@@ -159,10 +155,9 @@ void main() {
|
|||||||
testWidgets('navigates to next picture when right button is tapped', (WidgetTester tester) async {
|
testWidgets('navigates to next picture when right button is tapped', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto1),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Verify first picture comment is shown
|
// Verify first picture comment is shown
|
||||||
@@ -180,10 +175,9 @@ void main() {
|
|||||||
testWidgets('navigates to previous picture when left button is tapped', (WidgetTester tester) async {
|
testWidgets('navigates to previous picture when left button is tapped', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 1024, 1024);
|
setScreenSize(tester, 1024, 1024);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto2),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/2");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Verify second picture comment is shown
|
// Verify second picture comment is shown
|
||||||
@@ -208,10 +202,9 @@ void main() {
|
|||||||
pictures: [pictureDto1],
|
pictures: [pictureDto1],
|
||||||
);
|
);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => singlePictureCustomer);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: singlePictureCustomer, pictureDto: pictureDto1),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Both navigation buttons should be shown but disabled
|
// Both navigation buttons should be shown but disabled
|
||||||
@@ -222,10 +215,9 @@ void main() {
|
|||||||
testWidgets('opens fullscreen dialog when image is tapped', (WidgetTester tester) async {
|
testWidgets('opens fullscreen dialog when image is tapped', (WidgetTester tester) async {
|
||||||
setScreenSize(tester, 2048, 2048);
|
setScreenSize(tester, 2048, 2048);
|
||||||
|
|
||||||
await pumpApp(
|
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
|
||||||
tester,
|
|
||||||
PictureWidget(customerDto: customerDto, pictureDto: pictureDto1),
|
await pumpAppConfig(tester, "${GlobalRouter.pathPicture}/1/1");
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Find the GestureDetector with the image key
|
// Find the GestureDetector with the image key
|
||||||
|
|||||||
Reference in New Issue
Block a user