From c7ff754dd80ad353fe9210e84cfa044c45ceae5d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 6 Feb 2024 10:48:46 +0300 Subject: [PATCH] Password expired fixes & ITG Attachments implementation --- lib/api/worklist/worklist_api_client.dart | 16 +++ lib/extensions/string_extensions.dart | 3 +- lib/models/dashboard/itg_forms_model.dart | 10 +- .../itg_forms_attachments_model.dart | 25 +++++ lib/ui/login/new_password_screen.dart | 4 +- lib/ui/work_list/itg_detail_screen.dart | 50 ++++++++- .../itg_fragments/attachments_fragment.dart | 100 ++++++++++++++++++ .../attachments_fragment.dart | 31 +++++- 8 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 lib/models/itg_forms_models/itg_forms_attachments_model.dart create mode 100644 lib/ui/work_list/itg_fragments/attachments_fragment.dart diff --git a/lib/api/worklist/worklist_api_client.dart b/lib/api/worklist/worklist_api_client.dart index 611bcf4..d2389ab 100644 --- a/lib/api/worklist/worklist_api_client.dart +++ b/lib/api/worklist/worklist_api_client.dart @@ -21,6 +21,7 @@ import 'package:mohem_flutter_app/models/get_quotation_analysis_list_model.dart' import 'package:mohem_flutter_app/models/get_stamp_ms_notification_body_list_model.dart'; import 'package:mohem_flutter_app/models/get_stamp_ns_notification_body_list_model.dart'; import 'package:mohem_flutter_app/models/get_user_item_type_list.dart'; +import 'package:mohem_flutter_app/models/itg_forms_models/itg_forms_attachments_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/itg_request_model.dart'; import 'package:mohem_flutter_app/models/member_information_list_model.dart'; import 'package:mohem_flutter_app/models/notification_get_respond_attributes_list_model.dart'; @@ -364,6 +365,21 @@ class WorkListApiClient { }, url, postParams); } + Future?> getITGFormAttachments(String requestType, taskId, itemId, String employeeNumber) async { + String url = "${ApiConsts.cocRest}ITGGetFormDetialsAttachment"; + Map postParams = { + "RequestType": requestType, + "TaskID": taskId, + "ItemIDStr": itemId, + "EmployeeNumber": employeeNumber, + }; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject((json) { + ItgFormsModel responseData = ItgFormsModel.fromJson(json); + return responseData.itgFormAttachmentsList; + }, url, postParams); + } + Future rejectITGRequest(String requestType, taskId, itemId, String employeeNumber, String comments) async { String url = "${ApiConsts.cocRest}ITGRejectRequest"; Map postParams = { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 3af55f0..91ded82 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -68,8 +68,9 @@ extension EmailValidator on String { style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), ); - Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int maxLine = 0}) => Text( + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( this, + textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( fontSize: 11, diff --git a/lib/models/dashboard/itg_forms_model.dart b/lib/models/dashboard/itg_forms_model.dart index ae67ef8..7109d4e 100644 --- a/lib/models/dashboard/itg_forms_model.dart +++ b/lib/models/dashboard/itg_forms_model.dart @@ -4,6 +4,7 @@ import 'dart:convert'; +import 'package:mohem_flutter_app/models/itg_forms_models/itg_forms_attachments_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/itg_request_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/request_type_model.dart'; @@ -84,7 +85,8 @@ class ItgFormsModel { dynamic successMsgN; dynamic vidaUpdatedResponse; ITGRequest? itgRequest; - dynamic itgFormAttachmentsList; + // dynamic itgFormAttachmentsList; + List? itgFormAttachmentsList; String? message; dynamic mohemmItgDepartmentSectionsList; dynamic mohemmItgProjectDepartmentsList; @@ -128,7 +130,11 @@ class ItgFormsModel { successMsgN: json["SuccessMsgN"], vidaUpdatedResponse: json["VidaUpdatedResponse"], itgRequest: json['ITGRequest'] != null ? ITGRequest.fromJson(json['ITGRequest']) : null, - itgFormAttachmentsList: json["Itg_FormAttachmentsList"], + itgFormAttachmentsList: + json["Itg_FormAttachmentsList"] == null ? [] : json["Itg_FormAttachmentsList"]!.map((v) => ITGFormsAttachmentsModel.fromJson(v)).toList(), + + // json["RequestType"] == null ? [] : json['RequestType']!.map((v) => RequestType.fromJson(v)).toList(), + message: json["Message"] == null ? null : json["Message"], mohemmItgDepartmentSectionsList: json["Mohemm_ITG_DepartmentSectionsList"], mohemmItgProjectDepartmentsList: json["Mohemm_ITG_ProjectDepartmentsList"], diff --git a/lib/models/itg_forms_models/itg_forms_attachments_model.dart b/lib/models/itg_forms_models/itg_forms_attachments_model.dart new file mode 100644 index 0000000..2f5d8b5 --- /dev/null +++ b/lib/models/itg_forms_models/itg_forms_attachments_model.dart @@ -0,0 +1,25 @@ +class ITGFormsAttachmentsModel { + String? fileBase64; + dynamic fileData; + String? fileName; + String? fileType; + + ITGFormsAttachmentsModel( + {this.fileBase64, this.fileData, this.fileName, this.fileType}); + + ITGFormsAttachmentsModel.fromJson(Map json) { + fileBase64 = json['fileBase64']; + fileData = json['fileData']; + fileName = json['fileName']; + fileType = json['fileType']; + } + + Map toJson() { + Map data = new Map(); + data['fileBase64'] = this.fileBase64; + data['fileData'] = this.fileData; + data['fileName'] = this.fileName; + data['fileType'] = this.fileType; + return data; + } +} diff --git a/lib/ui/login/new_password_screen.dart b/lib/ui/login/new_password_screen.dart index 11ba74b..70b7456 100644 --- a/lib/ui/login/new_password_screen.dart +++ b/lib/ui/login/new_password_screen.dart @@ -102,8 +102,8 @@ class _NewPasswordScreenState extends State { passwordConstraintsUI(LocaleKeys.minimum8Characters.tr(), password.text.length >= 8), 8.height, passwordConstraintsUI(LocaleKeys.doNotAddRepeatingLetters.tr(), checkRepeatedChars(password.text)), - 8.height, - passwordConstraintsUI(LocaleKeys.itShouldContainSpecialCharacter.tr(), checkRegEx(r'^[a-zA-Z0-9]+$')), + // 8.height, + // passwordConstraintsUI(LocaleKeys.itShouldContainSpecialCharacter.tr(), checkRegEx(r'^[a-zA-Z0-9]+$')), 8.height, passwordConstraintsUI(LocaleKeys.confirmPasswordMustMatch.tr(), password.text.isNotEmpty && password.text == confirmPassword.text), ], diff --git a/lib/ui/work_list/itg_detail_screen.dart b/lib/ui/work_list/itg_detail_screen.dart index 5d48c53..546d320 100644 --- a/lib/ui/work_list/itg_detail_screen.dart +++ b/lib/ui/work_list/itg_detail_screen.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:lottie/lottie.dart'; import 'package:mohem_flutter_app/api/worklist/worklist_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; @@ -12,10 +13,12 @@ import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/allowed_actions_model.dart'; +import 'package:mohem_flutter_app/models/itg_forms_models/itg_forms_attachments_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/itg_request_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/request_detail_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/work_list/itg_fragments/approval_level_fragment.dart'; +import 'package:mohem_flutter_app/ui/work_list/itg_fragments/attachments_fragment.dart'; import 'package:mohem_flutter_app/ui/work_list/itg_fragments/request_detail_fragment.dart'; import 'package:mohem_flutter_app/ui/work_list/sheets/delegate_sheet.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; @@ -44,9 +47,12 @@ class _ItgDetailScreenState extends State { bool isRejectAvailable = false; List allowedActionList = []; + List itgFormAttachmentsList = []; late DashboardProviderModel providerData; + bool isAttachmentLoaded = false; + @override void initState() { providerData = Provider.of(context, listen: false); @@ -87,6 +93,7 @@ class _ItgDetailScreenState extends State { requestDetails = AppState().requestAllList![AppState().itgWorkListIndex!]; // ModalRoute.of(context)!.settings.arguments as WorkListResponseModel; providerData.itgFormsModel!.totalCount = providerData.itgFormsModel!.totalCount! - 1; getItgData(); + getItgRequestAttachments(); } } @@ -140,6 +147,7 @@ class _ItgDetailScreenState extends State { myTab(LocaleKeys.requestDetails.tr(), 0), myTab(LocaleKeys.approvalLevel.tr(), 1), myTab(LocaleKeys.requesterDetails.tr(), 2), + myTab(LocaleKeys.attachments.tr(), 3), ], ), ), @@ -157,6 +165,11 @@ class _ItgDetailScreenState extends State { voidCallback: reloadITG, ), RequestDetailFragment(fields: itgRequest?.fieldGoups?[0].fields ?? []), + isAttachmentLoaded + ? itgFormAttachmentsList.isEmpty + ? Utils.getNoDataWidget(context) + : ITGAttachmentsFragment(itgFormAttachmentsList) + : showLoadingAnimation(), ], ).expanded, if (isApproveAvailable || isRejectAvailable || isCloseAvailable) @@ -405,7 +418,7 @@ class _ItgDetailScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - title.toText12(color: isSelected ? Colors.white : Colors.white.withOpacity(.74), isCenter: true), + title.toText11(color: isSelected ? Colors.white : Colors.white.withOpacity(.74), isCenter: true), 4.height, Container( height: 8, @@ -478,7 +491,7 @@ class _ItgDetailScreenState extends State { ); } - void performAnswerAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { + void performAnswerAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { try { Utils.showLoading(context); ITGRequest? itgRequest = await WorkListApiClient().answerITGRequest(requestType, taskId, itemId, employeeNumber, "", comments); @@ -502,7 +515,7 @@ class _ItgDetailScreenState extends State { } } - void performRejectAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { + void performRejectAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { try { Utils.showLoading(context); ITGRequest? itgRequest = await WorkListApiClient().rejectITGRequest(requestType, taskId, itemId, employeeNumber, comments); @@ -526,7 +539,7 @@ class _ItgDetailScreenState extends State { } } - void performApproveAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { + void performApproveAction(String requestType, taskId, itemId, String employeeNumber, String comments) async { try { Utils.showLoading(context); ITGRequest? itgRequest = await WorkListApiClient().approveITGRequest(requestType, taskId, itemId, employeeNumber, comments); @@ -576,6 +589,35 @@ class _ItgDetailScreenState extends State { } } + Widget showLoadingAnimation() { + return Lottie.asset( + 'assets/lottie/loading.json', + repeat: true, + reverse: false, + ); + } + + void getItgRequestAttachments() async { + try { + // Utils.showLoading(context); + itgFormAttachmentsList = + (await WorkListApiClient().getITGFormAttachments(requestDetails!.requestType!, requestDetails!.iD, requestDetails!.itemID, AppState().memberInformationList?.eMPLOYEENUMBER ?? ""))!; + // allowedActionList = itgRequest?.allowedActions ?? []; + // if (allowedActionList.isNotEmpty) { + // isCloseAvailable = allowedActionList.any((element) => element.action == "CLOSE"); + // isApproveAvailable = itgRequest!.allowedActions!.any((element) => element.action == "Approve"); + // isRejectAvailable = itgRequest!.allowedActions!.any((element) => element.action == "Reject"); + // } + // Utils.hideLoading(context); + setState(() { + isAttachmentLoaded = true; + }); + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + } + } + void reloadITG() { animationIndex = animationIndex + 1; AppState().requestAllList!.removeAt(AppState().itgWorkListIndex!); diff --git a/lib/ui/work_list/itg_fragments/attachments_fragment.dart b/lib/ui/work_list/itg_fragments/attachments_fragment.dart new file mode 100644 index 0000000..a4a8372 --- /dev/null +++ b/lib/ui/work_list/itg_fragments/attachments_fragment.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/models/itg_forms_models/itg_forms_attachments_model.dart'; +import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ITGAttachmentsFragment extends StatelessWidget { + List itgFormAttachmentsList; + + ITGAttachmentsFragment(this.itgFormAttachmentsList); + + @override + Widget build(BuildContext context) { + return ListView.separated( + itemCount: itgFormAttachmentsList.length, + itemBuilder: (context, index) { + return Row( + children: [ + SvgPicture.asset(determineFileIcon(itgFormAttachmentsList[index].fileType ?? "")), + 12.width, + (itgFormAttachmentsList[index].fileName ?? "").toText16().expanded, + ], + ).objectContainerView().onPress(() async { + try { + Permission.storage.isGranted.then( + (isGranted) { + if (!isGranted) { + Permission.manageExternalStorage.request().then( + (granted) async { + if (granted == PermissionStatus.granted) { + String path = await _createFileFromString(itgFormAttachmentsList[index].fileBase64 ?? "", itgFormAttachmentsList[index].fileType ?? ""); + OpenFile.open(path); + } else { + showDialog( + context: context, + builder: (BuildContext cxt) => ConfirmDialog( + message: "You need to give storage permission to view files.", + onTap: () { + Navigator.pop(context); + }, + ), + ); + } + }, + ); + } + }, + ); + } catch (ex) { + Utils.showToast("Cannot open file."); + } + }); + }, + separatorBuilder: (BuildContext context, int index) => 12.height, + ).paddingAll(21); + } + + String determineFileIcon(String fileContentType) { + String icon = ""; + switch (fileContentType) { + case "pdf": + icon = "assets/images/pdf.svg"; + break; + case "xls": + icon = "assets/images/xls.svg"; + break; + case "xlsx": + icon = "assets/images/xls.svg"; + break; + case "png": + icon = "assets/images/png.svg"; + break; + case "jpg": + icon = "assets/images/jpg.svg"; + break; + case "jpeg": + icon = "assets/images/jpg.svg"; + break; + } + return icon; + } + + Future _createFileFromString(String encodedStr, String ext) async { + Uint8List bytes = base64.decode(encodedStr); + String dir = (await getApplicationDocumentsDirectory()).path; + File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); + await file.writeAsBytes(bytes); + return file.path; + } +} diff --git a/lib/ui/work_list/worklist_fragments/attachments_fragment.dart b/lib/ui/work_list/worklist_fragments/attachments_fragment.dart index bc1bde6..01a075b 100644 --- a/lib/ui/work_list/worklist_fragments/attachments_fragment.dart +++ b/lib/ui/work_list/worklist_fragments/attachments_fragment.dart @@ -9,8 +9,10 @@ import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/models/get_attachement_list_model.dart'; +import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; class AttachmentsFragment extends StatelessWidget { final List getAttachmentList; @@ -30,9 +32,32 @@ class AttachmentsFragment extends StatelessWidget { ], ).objectContainerView().onPress(() async { try { - String path = await _createFileFromString(getAttachmentList[index].fILEDATA ?? "", getAttachmentList[index].fILECONTENTTYPE ?? ""); - OpenFile.open(path); - } catch (ex) {Utils.showToast("Cannot open file."); + Permission.storage.isGranted.then( + (isGranted) { + if (!isGranted) { + Permission.manageExternalStorage.request().then( + (granted) async { + if (granted == PermissionStatus.granted) { + String path = await _createFileFromString(getAttachmentList[index].fILEDATA ?? "", getAttachmentList[index].fILECONTENTTYPE ?? ""); + OpenFile.open(path); + } else { + showDialog( + context: context, + builder: (BuildContext cxt) => ConfirmDialog( + message: "You need to give storage permission to view files.", + onTap: () { + Navigator.pop(context); + }, + ), + ); + } + }, + ); + } + }, + ); + } catch (ex) { + Utils.showToast("Cannot open file."); } }); },