diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 5eb2c59..f09dfb4 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -164,6 +164,7 @@ "quotationAnalysis": "تحليل الاقتباس", "subject": "موضوعات", "description": "وصف", + "pleaseEnterDescription": "الرجاء إدخال الوصف", "supplier": "المورد", "site": "موقع", "buyer": "مشتر", @@ -258,6 +259,7 @@ "fullName": "الأسم الكامل", "remove": "حذف", "submit": "ارسال", + "submitAsAnonymous": "إرسال كمجهول", "areYouSureYouWantToSubmit": "هل أنت متأكد أنك تريد أن تقدم؟", "comments": "تعليقات", "writeComment": "أكتب تعليقا", @@ -655,6 +657,18 @@ "pullToRefresh": "اسحب للتحديث", "courseUrlNotAvailable": "رابط الدورة غير متوفر", "buyerDetails": "تفاصيل المشتري", + "etqanOVR": "الإبلاغ عن حادثة", + "etqanRequest": "طلبات الحوادث", + "selectProject": "حدد المشروع", + "project": "مشروع", + "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", + "ticketNumber": "رقم التذكرة", + "ticketId": "معرف التذكرة", + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment" : "عرض المرفق", "parkingQr": "وقوف السيارات QR" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index faa29f1..216fe85 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -158,6 +158,7 @@ "subject": "Subject", "quotationAnalysis": "Quotation Analysis", "description": "Description", + "pleaseEnterDescription": "Please enter description", "supplier": "Supplier", "site": "Site", "buyer": "Buyer", @@ -256,6 +257,7 @@ "remove": "Remove", "Attendance": "Attendance", "submit": "Submit", + "submitAsAnonymous": "Submit as Anonymous", "areYouSureYouWantToSubmit": "Are you sure you want to submit?", "comments": "Comments", "writeComment": "Write a comment", @@ -653,5 +655,17 @@ "pullToRefresh": "Pull to refresh", "courseUrlNotAvailable": "Course URL not available", "buyerDetails": "Buyer Details", + "etqanOVR": "Report An Incident", + "etqanRequest": "Incident Requests", + "selectProject": "Select Project", + "project": "Project", + "keen": "Keen", + "reportIncident": "Report Incident", + "myIncidents": "My Incidents", + "incidentDetails": "Incident Details", + "ticketNumber": "Ticket Number", + "ticketId": "Ticket ID", + "pleaseSelectProject": "Please select a project", + "viewAttachment" : "View Attachment", "parkingQr": "Parking QR" } \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 01197de..51a5801 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -61,6 +61,8 @@ This app requires photo library access to select image as document & upload it. UIApplicationSupportsIndirectInputEvents + NSSpeechRecognitionUsageDescription + This app requires speech recognition access to access voice command features. UIBackgroundModes fetch diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart index cd8abee..27fe289 100644 --- a/lib/api/dashboard_api_client.dart +++ b/lib/api/dashboard_api_client.dart @@ -231,7 +231,8 @@ class DashboardApiClient { // token = // "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhjZTE2OWM0YjIwYjQ2ZWM5YTQyOTU3Y2ZhODUzNzQ1IiwidHlwIjoiSldUIn0.eyJ0ZW5hbnRfaWQiOiJhOWY0ZDFhMDU5NmQ0YWVhOGY4MzA5OTJlYzRiZGFjMSIsImVpZCI6IjExNzkzMCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3NpZCI6Ijk2MDI0OGM1NzA3YzQ3MmFhYTEzM2I1N2ZhODE1ZmVhIiwibGFuZ3VhZ2UiOiJVUyIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI6IjExNzkzMEBobWcuY29tIiwiZXhwIjoxNzgyNDc1NzY5LCJpc3MiOiJodHRwczovL3Nzby11YXQuaG1nLmNvbSIsImF1ZCI6ImE5ZjRkMWEwNTk2ZDRhZWE4ZjgzMDk5MmVjNGJkYWMxIn0.rJcLVsG8D0XECyLERCTD2uqGeWyvp-OBVGE9uL2qKrX4etFUHgdFt_5kYF6edFTtGy-0PIZadHDmv7e-IOhVWHm5HVMClaukiXoRXR8cDN8XA1wfme3Kd-U5PXN-IRh49AyRTzLO0rYNPvH81ScosWGlsFSkOvA-0hJNa2adHdtvgNvB8wJshSU5p7sAmF8mjdDY6aInG19etu2iEuUDwHHA4ZY_ts4hboHo8fE392hFaYGonExoD7bpW5RMx5xKWeRCmWpG_PK8Aw_z1jGzdB9PANus4pteRGuln1J-kmo2lQC9pVrSyZATAKp1HfgfyZ_vUhaHEfM69cMWaCslJQ"; // // var request = http.MultipartRequest('POST', Uri.parse('https://ek.techmaster.in/SSO/HMG')); - var request = http.MultipartRequest('POST', Uri.parse('https://Paxes-release.techmaster.in/SSO/HMG')); + var request = http.MultipartRequest('POST', Uri.parse('https://Paxes-release.techmaster.in/SSO/HMG')); // UAT + // var request = http.MultipartRequest('POST', Uri.parse('https://paxes.com/sso/hmg')); // Prod request.fields.addAll({'JWTToken': token}); // request.headers.addAll(headers); diff --git a/lib/api/etqan_ovr_api_client.dart b/lib/api/etqan_ovr_api_client.dart new file mode 100644 index 0000000..f7512cc --- /dev/null +++ b/lib/api/etqan_ovr_api_client.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:mohem_flutter_app/api/api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/consts.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:mohem_flutter_app/models/generic_response_model.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_department_sections.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_project_departments.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_projects.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_section_topics.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_details.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_transactions.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_types.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_tickets_list.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_transaction_attachment_model.dart'; + +class EtqanApiClient { + static final EtqanApiClient _instance = EtqanApiClient._internal(); + + EtqanApiClient._internal(); + + factory EtqanApiClient() => _instance; + + Future> getEtqanProjects() async { + String url = "${ApiConsts.cocRest}ETQAN_GetProjects"; + Map postParams = {"P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List? responseData = []; + if (json["ETQAN_GetProjects_Response"] != null && json["ETQAN_GetProjects_Response"].isNotEmpty) { + json["ETQAN_GetProjects_Response"].forEach((element) { + responseData.add(EtqanGetProjectsResponse.fromJson(element)); + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future> getEmployeeEtqanRequests() async { + String url = "${ApiConsts.cocRest}ETQAN_GetEmployeeOVRRequests"; + Map postParams = { + "ETQAN_EmployeeID": AppState().memberInformationList?.eMPLOYEENUMBER, + "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER, + }; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List responseData = []; + if (json["ETQAN_GetEmployeeOVRRequests_Response"] != null && json["ETQAN_GetEmployeeOVRRequests_Response"].isNotEmpty) { + json["ETQAN_GetEmployeeOVRRequests_Response"].forEach((element) { + responseData.add(EtqanGetEmployeeOvrRequestsResponse.fromJson(element)); + }); + } + + if (responseData.isNotEmpty) { + responseData.sort((EtqanGetEmployeeOvrRequestsResponse a, EtqanGetEmployeeOvrRequestsResponse b) { + if (a.createdDate == null && b.createdDate == null) return 0; + if (a.createdDate == null) return 1; + if (b.createdDate == null) return -1; + + try { + // Parse dates in format "26-Feb-2026 12:54 PM" or "01-Mar-2026 02:45 PM" + DateFormat dateFormat = DateFormat("dd-MMM-yyyy hh:mm a", "en_US"); + DateTime dateA = dateFormat.parse(a.createdDate!); + DateTime dateB = dateFormat.parse(b.createdDate!); + return dateB.compareTo(dateA); // Descending order (newest first) + } catch (e) { + return (b.createdDate ?? '').compareTo(a.createdDate ?? ''); + } + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future> getEtqanIncidentRequests(String ticketNo) async { + String url = "${ApiConsts.cocRest}ETQAN_GetIncidentRequest"; + Map postParams = {"ETQAN_TicketNumber": ticketNo, "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List responseData = []; + if (json["ETQAN_GetIncidentRequest_Response"] != null && json["ETQAN_GetIncidentRequest_Response"].isNotEmpty) { + json["ETQAN_GetIncidentRequest_Response"].forEach((element) { + responseData.add(EtqanGetIncidentRequestResponse.fromJson(element)); + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future?> createRequest(String description, int? projectID, String? attachmentBase64, bool? isAnonymous) async { + String url = "${ApiConsts.cocRest}ETQAN_CreateIncident"; + Map postParams = { + "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER, + "P_USER_NAME": AppState().memberInformationList?.eMPLOYEENUMBER, + "ETQAN_Emp_EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, + "ETQAN_Description": description, + "ETQAN_ProjectId": projectID, + "ETQAN_FileInfoBase64": attachmentBase64, + "ETQAN_IsAnonymous": isAnonymous ?? false, + }; + + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + if (json["ETQAN_CreateIncident_Response"] != null) { + var response = json["ETQAN_CreateIncident_Response"]; + if (response["id"] != null && response["ticketNumber"] != null) { + return {"id": response["id"], "ticketNumber": response["ticketNumber"]}; + } + return null; + } + return null; + + // "ETQAN_CreateIncident_Response": { + // "id": "00adabe4-4a32-4b35-1f00-08de4d152e7c", + // "ticketNumber": "OVR/OLY/2026/224" + // }, + }, + url, + postParams, + ); + } +} diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index a6b7173..d8efe5d 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -11,7 +11,7 @@ class ApiConsts { // static String baseUrl = "https://mohemm.hmg.com"; // New Live server // - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + // static String baseUrl = "http://10.20.200.111:1010/"; // static String baseUrl = "https://webservices.hmg.com"; // PreProd @@ -27,7 +27,8 @@ class ApiConsts { static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; - static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect"; + static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect"; // UAT + // static String ssoAuthRedirection = "https://sso.hmg.com/api/auth/connect"; // Prod //Chat static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 6a9b16b..2ae4fe3 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -135,8 +135,8 @@ class Utils { } } - static Future showErrorDialog({required BuildContext context, required VoidCallback onOkTapped, required String message}) async { - return showDialog(context: context, builder: (BuildContext context) => ConfirmDialog(message: message, onTap: onOkTapped)); + static Future showErrorDialog({required BuildContext context, required VoidCallback onOkTapped, required String message, VoidCallback? onCloseTap}) async { + return showDialog(context: context, builder: (BuildContext context) => ConfirmDialog(message: message, onTap: onOkTapped, onCloseTap: onCloseTap)); } static void confirmDialog(cxt, String message, {VoidCallback? onTap}) { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index a864e32..9bae8df 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -10,6 +10,9 @@ import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart'; import 'package:mohem_flutter_app/ui/chat/group_members.dart'; import 'package:mohem_flutter_app/ui/chat/manage_group.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart'; @@ -216,6 +219,10 @@ class AppRoutes { static const String activityScreen = "/activityScreen"; + static const String etqanOvr = "/etqanOvr"; + static const String etqanCreateRequest = "/etqanCreateRequest"; + static const String etqanGetIncidientRequest = "/etqanGetIncidientRequest"; + //Parking QR static const String parkingQr = "/parkingQrScreen"; @@ -341,6 +348,12 @@ class AppRoutes { childEducation: (BuildContext context) => ChildEducationAssistance(), activityScreen:(BuildContext context) => const EventActivityScreen(), + //ETQAN OVR + etqanOvr: (BuildContext context) => EtqanOvrHome(), + etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(), + etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(), + activityScreen:(BuildContext context) => const EventActivityScreen(), + //Parking QR parkingQr: (BuildContext context) => const ParkingQrScreen(), diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 4a92eef..9ef43c9 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -180,6 +180,7 @@ class CodegenLoader extends AssetLoader{ "quotationAnalysis": "تحليل الاقتباس", "subject": "موضوعات", "description": "وصف", + "pleaseEnterDescription": "الرجاء إدخال الوصف", "supplier": "المورد", "site": "موقع", "buyer": "مشتر", @@ -274,6 +275,7 @@ class CodegenLoader extends AssetLoader{ "fullName": "الأسم الكامل", "remove": "حذف", "submit": "ارسال", + "submitAsAnonymous": "إرسال كمجهول", "areYouSureYouWantToSubmit": "هل أنت متأكد أنك تريد أن تقدم؟", "comments": "تعليقات", "writeComment": "أكتب تعليقا", @@ -668,6 +670,18 @@ class CodegenLoader extends AssetLoader{ "pullToRefresh": "اسحب للتحديث", "courseUrlNotAvailable": "رابط الدورة غير متوفر", "buyerDetails": "تفاصيل المشتري", + "etqanOVR": "الإبلاغ عن حادثة", + "etqanRequest": "طلبات الحوادث", + "selectProject": "حدد المشروع", + "project": "مشروع", + "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", + "ticketNumber": "رقم التذكرة", + "ticketId": "معرف التذكرة", + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment": "عرض المرفق", "parkingQr": "وقوف السيارات QR" }; static const Map _en_US = { @@ -830,6 +844,7 @@ static const Map _en_US = { "subject": "Subject", "quotationAnalysis": "Quotation Analysis", "description": "Description", + "pleaseEnterDescription": "Please enter description", "supplier": "Supplier", "site": "Site", "buyer": "Buyer", @@ -928,6 +943,7 @@ static const Map _en_US = { "remove": "Remove", "Attendance": "Attendance", "submit": "Submit", + "submitAsAnonymous": "Submit as Anonymous", "areYouSureYouWantToSubmit": "Are you sure you want to submit?", "comments": "Comments", "writeComment": "Write a comment", @@ -1324,6 +1340,18 @@ static const Map _en_US = { "pullToRefresh": "Pull to refresh", "courseUrlNotAvailable": "Course URL not available", "buyerDetails": "Buyer Details", + "etqanOVR": "Report An Incident", + "etqanRequest": "Incident Requests", + "selectProject": "Select Project", + "project": "Project", + "keen": "Keen", + "reportIncident": "Report Incident", + "myIncidents": "My Incidents", + "incidentDetails": "Incident Details", + "ticketNumber": "Ticket Number", + "ticketId": "Ticket ID", + "pleaseSelectProject": "Please select a project", + "viewAttachment": "View Attachment", "parkingQr": "Parking QR" }; static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 57aad94..a9f4584 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -167,6 +167,7 @@ abstract class LocaleKeys { static const quotationAnalysis = 'quotationAnalysis'; static const subject = 'subject'; static const description = 'description'; + static const pleaseEnterDescription = 'pleaseEnterDescription'; static const supplier = 'supplier'; static const site = 'site'; static const buyer = 'buyer'; @@ -261,6 +262,7 @@ abstract class LocaleKeys { static const fullName = 'fullName'; static const remove = 'remove'; static const submit = 'submit'; + static const submitAsAnonymous = 'submitAsAnonymous'; static const areYouSureYouWantToSubmit = 'areYouSureYouWantToSubmit'; static const comments = 'comments'; static const writeComment = 'writeComment'; @@ -640,6 +642,18 @@ abstract class LocaleKeys { static const pullToRefresh = 'pullToRefresh'; static const courseUrlNotAvailable = 'courseUrlNotAvailable'; static const buyerDetails = 'buyerDetails'; + static const etqanOVR = 'etqanOVR'; + static const etqanRequest = 'etqanRequest'; + static const selectProject = 'selectProject'; + static const project = 'project'; + static const keen = 'keen'; + static const reportIncident = 'reportIncident'; + static const myIncidents = 'myIncidents'; + static const incidentDetails = 'incidentDetails'; + static const ticketNumber = 'ticketNumber'; + static const ticketId = 'ticketId'; + static const pleaseSelectProject = 'pleaseSelectProject'; + static const viewAttachment = 'viewAttachment'; static const parkingQr = 'parkingQr'; } diff --git a/lib/main.dart b/lib/main.dart index 263ed8e..022204d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:mohem_flutter_app/models/post_params_model.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/eit_provider_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; import 'package:mohem_flutter_app/theme/app_theme.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:month_year_picker/month_year_picker.dart'; @@ -53,6 +54,7 @@ Future main() async { ChangeNotifierProvider(create: (_) => EITProviderModel()), ChangeNotifierProvider(create: (_) => ChatProviderModel()), ChangeNotifierProvider(create: (_) => MarathonProvider()), + ChangeNotifierProvider(create: (_) => EtqanOvrProviderModel()), // ChangeNotifierProvider( // create: (_) => ChatCallProvider(), // ), diff --git a/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart new file mode 100644 index 0000000..e4a57fd --- /dev/null +++ b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +class EtqanGetEmployeeIncidentRequestModel { + List? etqanGetIncidentRequestResponse; + + EtqanGetEmployeeIncidentRequestModel({ + this.etqanGetIncidentRequestResponse, + }); + + factory EtqanGetEmployeeIncidentRequestModel.fromRawJson(String str) => EtqanGetEmployeeIncidentRequestModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeIncidentRequestModel.fromJson(Map json) => EtqanGetEmployeeIncidentRequestModel( + etqanGetIncidentRequestResponse: json["ETQAN_GetIncidentRequest_Response"] == null ? [] : List.from(json["ETQAN_GetIncidentRequest_Response"]!.map((x) => EtqanGetIncidentRequestResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetIncidentRequest_Response": etqanGetIncidentRequestResponse == null ? [] : List.from(etqanGetIncidentRequestResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetIncidentRequestResponse { + String? key; + String? keyAr; + String? value; + String? valueAr; + List? subValues; + + EtqanGetIncidentRequestResponse({ + this.key, + this.keyAr, + this.value, + this.valueAr, + this.subValues, + }); + + factory EtqanGetIncidentRequestResponse.fromRawJson(String str) => EtqanGetIncidentRequestResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetIncidentRequestResponse.fromJson(Map json) => EtqanGetIncidentRequestResponse( + key: json["key"], + keyAr: json["keyAr"], + value: json["value"], + valueAr: json["valueAr"], + subValues: json["subValues"] == null ? [] : List.from(json["subValues"]!.map((x) => x)), + ); + + Map toJson() => { + "key": key, + "keyAr": keyAr, + "value": value, + "valueAr": valueAr, + "subValues": subValues == null ? [] : List.from(subValues!.map((x) => x)), + }; +} diff --git a/lib/models/etqan_ovr/etqan_get_requests_model.dart b/lib/models/etqan_ovr/etqan_get_requests_model.dart new file mode 100644 index 0000000..ac4911b --- /dev/null +++ b/lib/models/etqan_ovr/etqan_get_requests_model.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +class EtqanGetEmployeeRequestsModel { + List? etqanGetEmployeeOvrRequestsResponse; + + EtqanGetEmployeeRequestsModel({ + this.etqanGetEmployeeOvrRequestsResponse, + }); + + factory EtqanGetEmployeeRequestsModel.fromRawJson(String str) => EtqanGetEmployeeRequestsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeRequestsModel.fromJson(Map json) => EtqanGetEmployeeRequestsModel( + etqanGetEmployeeOvrRequestsResponse: json["ETQAN_GetEmployeeOVRRequests_Response"] == null ? [] : List.from(json["ETQAN_GetEmployeeOVRRequests_Response"]!.map((x) => EtqanGetEmployeeOvrRequestsResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetEmployeeOVRRequests_Response": etqanGetEmployeeOvrRequestsResponse == null ? [] : List.from(etqanGetEmployeeOvrRequestsResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetEmployeeOvrRequestsResponse { + String? createdDate; + String? description; + String? id; + bool? isAnonymous; + String? status; + String? statusAr; + String? ticketNumber; + + EtqanGetEmployeeOvrRequestsResponse({ + this.createdDate, + this.description, + this.id, + this.isAnonymous, + this.status, + this.statusAr, + this.ticketNumber, + }); + + factory EtqanGetEmployeeOvrRequestsResponse.fromRawJson(String str) => EtqanGetEmployeeOvrRequestsResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeOvrRequestsResponse.fromJson(Map json) => EtqanGetEmployeeOvrRequestsResponse( + createdDate: json["createdDate"], + description: json["description"], + id: json["id"], + isAnonymous: json["isAnonymous"], + status: json["status"], + statusAr: json["statusAr"], + ticketNumber: json["ticketNumber"], + ); + + Map toJson() => { + "createdDate": createdDate, + "description": description, + "id": id, + "isAnonymous": isAnonymous, + "status": status, + "statusAr": statusAr, + "ticketNumber": ticketNumber, + }; +} diff --git a/lib/models/etqan_ovr/etqan_getprojects_model.dart b/lib/models/etqan_ovr/etqan_getprojects_model.dart new file mode 100644 index 0000000..5584faa --- /dev/null +++ b/lib/models/etqan_ovr/etqan_getprojects_model.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +class EtqanGetProjectsModel { + List? etqanGetProjectsResponse; + + EtqanGetProjectsModel({ + this.etqanGetProjectsResponse, + }); + + factory EtqanGetProjectsModel.fromRawJson(String str) => EtqanGetProjectsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetProjectsModel.fromJson(Map json) => EtqanGetProjectsModel( + etqanGetProjectsResponse: json["ETQAN_GetProjects_Response"] == null ? [] : List.from(json["ETQAN_GetProjects_Response"]!.map((x) => EtqanGetProjectsResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetProjects_Response": etqanGetProjectsResponse == null ? [] : List.from(etqanGetProjectsResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetProjectsResponse { + String? projectAr; + String? projectCode; + String? projectEn; + int? projectId; + + EtqanGetProjectsResponse({ + this.projectAr, + this.projectCode, + this.projectEn, + this.projectId, + }); + + factory EtqanGetProjectsResponse.fromRawJson(String str) => EtqanGetProjectsResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetProjectsResponse.fromJson(Map json) => EtqanGetProjectsResponse( + projectAr: json["projectAr"], + projectCode: json["projectCode"], + projectEn: json["projectEn"], + projectId: json["projectId"], + ); + + Map toJson() => { + "projectAr": projectAr, + "projectCode": projectCode, + "projectEn": projectEn, + "projectId": projectId, + }; +} diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index 0799aef..be8617d 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -126,7 +126,9 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { DrawerMenuItem("assets/images/drawer/my_profile.svg", LocaleKeys.myProfile.tr(), AppRoutes.profile), DrawerMenuItem("assets/images/drawer/performance_evaluation.svg", LocaleKeys.performanceEvaluation.tr(), AppRoutes.performanceEvaluation), DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.mowadhafhi.tr(), AppRoutes.mowadhafhi), - DrawerMenuItem("assets/images/drawer/car_parking_icon.svg", LocaleKeys.parkingQr.tr(), AppRoutes.parkingQr), + // DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.etqanOVR.tr(), AppRoutes.etqanOvr), + DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.keen.tr(), AppRoutes.etqanOvr), + // DrawerMenuItem("assets/images/drawer/car_parking_icon.svg", LocaleKeys.parkingQr.tr(), AppRoutes.parkingQr), DrawerMenuItem("assets/images/drawer/pending_trasactions.svg", LocaleKeys.pendingTransactions.tr(), AppRoutes.pendingTransactions), DrawerMenuItem("assets/images/drawer/courses.svg", "courses".tr(), AppRoutes.courses), // DrawerMenuItem("assets/images/drawer/drawer_marathon.svg", LocaleKeys.brainMarathon.tr(), AppRoutes.marathonIntroScreen), diff --git a/lib/provider/etqan_ovr_provider.dart b/lib/provider/etqan_ovr_provider.dart new file mode 100644 index 0000000..b4f2eb1 --- /dev/null +++ b/lib/provider/etqan_ovr_provider.dart @@ -0,0 +1,262 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/api/etqan_ovr_api_client.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + +class EtqanOvrProviderModel with ChangeNotifier, DiagnosticableTreeMixin { + // Home screen state + List? getEtqanProjectsList; + List? getEtqanEmployeeRequestsList; + List? getEtqanEmployeeIncidnetRequest; + bool isLoading = false; + String? _ticketId; + String? _currentStatus; + String? _currentStatusAr; + bool? _currentIsAnonymous; + + // Speech to text state + final SpeechToText _speechToText = SpeechToText(); + bool _speechEnabled = false; + bool _isListening = false; + String _currentLocaleId = ''; + TextEditingController? _currentController; + String _baseText = ''; // Text that was there when we started listening + String _currentSessionText = ''; // Text captured in current listening session + + // Getters for speech state + bool get speechEnabled => _speechEnabled; + + bool get isListening => _isListening; + + // Getter for ticketId + String get getTicketId => _ticketId ?? ''; + String get getCurrentStatus => _currentStatus ?? ''; + String get getCurrentStatusAr => _currentStatusAr ?? ''; + bool get getCurrentIsAnonymous => _currentIsAnonymous ?? false; + + // Setter for ticketId with status and isAnonymous + void setTicketId(String ticketId, {String? status, String? statusAr, bool? isAnonymous}) { + _ticketId = ticketId; + _currentStatus = status; + _currentStatusAr = statusAr; + _currentIsAnonymous = isAnonymous; + notifyListeners(); + } + + // Create request screen state + EtqanGetProjectsResponse? selectedProject; + String? selectedEmployeeNumber; + File? attachment; + String? attachmentBase64; + String description = ''; + bool isAnonymous = false; + + /// Initialize speech recognition - call this once when screen loads + Future initSpeech() async { + _speechEnabled = await _speechToText.initialize( + onStatus: (String status) { + print('Speech status: $status, isListening: $_isListening'); + // If user wants to keep listening but speech stopped, restart it + if ((status == 'done' || status == 'notListening') && _isListening && _currentController != null) { + // Save the finalized text before restarting + if (_currentSessionText.isNotEmpty) { + _baseText = _currentController!.text.trim(); + _currentSessionText = ''; + } + + // Automatically restart listening after a brief pause + Future.delayed(const Duration(milliseconds: 300), () { + if (_isListening && _currentController != null) { + _startListeningInternal(_currentLocaleId, _currentController!); + } + }); + } + }, + ); + notifyListeners(); + } + + /// Internal method to start listening + Future _startListeningInternal(String localeId, TextEditingController controller) async { + if (!_speechEnabled) return; + + await _speechToText.listen( + onResult: (SpeechRecognitionResult result) { + _currentSessionText = result.recognizedWords; + + // Combine base text with current session text + String combinedText = _baseText.isEmpty + ? _currentSessionText + : _baseText + (_baseText.endsWith(' ') ? '' : ' ') + _currentSessionText; + + description = combinedText; + controller.text = combinedText; + controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length)); + notifyListeners(); + }, + localeId: localeId, + listenFor: const Duration(seconds: 60), + pauseFor: const Duration(seconds: 10), + listenOptions: SpeechListenOptions( + partialResults: true, + listenMode: ListenMode.confirmation, + cancelOnError: false, + ), + ); + } + + /// Start listening for speech input + Future startListening(String localeId, TextEditingController controller) async { + _isListening = true; + _currentLocaleId = localeId; + _currentController = controller; + _baseText = controller.text.trim(); // Save existing text + _currentSessionText = ''; + notifyListeners(); + await _startListeningInternal(localeId, controller); + } + + /// Stop listening for speech input + Future stopListening() async { + _isListening = false; + _currentLocaleId = ''; + _currentController = null; + _baseText = ''; + _currentSessionText = ''; + await _speechToText.stop(); + notifyListeners(); + } + + /// Toggle listening state + Future toggleListening(String localeId, TextEditingController controller) async { + if (_isListening) { + await stopListening(); + } else { + await startListening(localeId, controller); + } + } + + // Fetch Etqan Projects + Future fetchEtqanProjects(BuildContext context) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanProjectsList?.clear(); + getEtqanProjectsList = await EtqanApiClient().getEtqanProjects(); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Fetch Etqan Employee Requests + Future fetchEtqanEmployeeRequests(BuildContext context) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanEmployeeRequestsList?.clear(); + getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests(); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Fetch Etqan Employee Incident Report + Future fetchEtqanEmployeeIncidentRequests(BuildContext context, String ticketNo) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanEmployeeIncidnetRequest?.clear(); + getEtqanEmployeeIncidnetRequest = await EtqanApiClient().getEtqanIncidentRequests(ticketNo); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Set selected project + void setSelectedProject(EtqanGetProjectsResponse? project) { + selectedProject = project; + notifyListeners(); + } + + // Set selected employee number + void setSelectedEmployeeNumber(String? employeeNumber) { + selectedEmployeeNumber = employeeNumber; + notifyListeners(); + } + + // Set attachment with base64 + void setAttachment(File file, String base64) { + attachment = file; + attachmentBase64 = base64; + notifyListeners(); + } + + // Remove attachment + void removeAttachment() { + attachment = null; + attachmentBase64 = null; + notifyListeners(); + } + + // Set description + void setDescription(String value) { + description = value; + notifyListeners(); + } + + // Validate description + bool isDescriptionValid() { + return description.trim().isNotEmpty; + } + + // Set isAnonymous + void setIsAnonymous(bool value) { + isAnonymous = value; + notifyListeners(); + } + + // Clear create request form + void clearCreateRequestForm() { + selectedProject = null; + selectedEmployeeNumber = null; + attachment = null; + attachmentBase64 = null; + description = ''; + isAnonymous = false; + notifyListeners(); + } + + // Submit request + Future?> submitRequest(BuildContext context) async { + try { + Utils.showLoading(context); + Map? response = await EtqanApiClient().createRequest(description, selectedProject?.projectId, attachmentBase64, isAnonymous); + Utils.hideLoading(context); + if (response != null) { + getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests(); + notifyListeners(); + return response; + } + return null; + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + return null; + } + } + + @override + void dispose() { + if (_isListening) { + _speechToText.stop(); + } + super.dispose(); + } +} diff --git a/lib/ui/etqan_ovr/etqan_create_request.dart b/lib/ui/etqan_ovr/etqan_create_request.dart new file mode 100644 index 0000000..a1d64af --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_create_request.dart @@ -0,0 +1,355 @@ +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/extensions/int_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/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:mohem_flutter_app/widgets/image_picker.dart'; +import 'package:provider/provider.dart'; + +class EtqanOvrCreateRequest extends StatefulWidget { + const EtqanOvrCreateRequest({Key? key}) : super(key: key); + + @override + _EtqanOvrCreateRequestState createState() => _EtqanOvrCreateRequestState(); +} + +class _EtqanOvrCreateRequestState extends State { + final TextEditingController _descriptionController = TextEditingController(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + var provider = context.read(); + provider.initSpeech(); + provider.fetchEtqanProjects(context); + provider.clearCreateRequestForm(); + }); + } + + @override + void dispose() { + // Stop listening if active + context.read().stopListening(); + _descriptionController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.reportIncident.tr(), showHomeButton: false), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + return Column( + children: [ + (ListView( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(21), + children: [ + _buildDropdownField( + label: LocaleKeys.project.tr(), + hint: LocaleKeys.selectProject.tr(), + value: _getProjectDisplayName(provider.selectedProject), + onTap: () => _showProjectSelectionSheet(context, provider), + isMandatory: true, + ), + 16.height, + // _buildDropdownField( + // label: 'Employee Number', + // hint: 'Write a message', + // value: provider.selectedEmployeeNumber, + // onTap: () { + // // TODO: Show employee selection dialog + // }, + // ), + // 16.height, + _buildAttachmentSection(provider), + 16.height, + _buildDescriptionField(provider), + 8.height, + _buildAnonymousCheckbox(provider), + 24.height, + ], + )).expanded, + DefaultButton(LocaleKeys.submit.tr(), () async { + provider.setDescription(_descriptionController.text); + if (provider.selectedProject == null) { + Utils.showErrorDialog( + context: context, + message: LocaleKeys.pleaseSelectProject.tr(), + onOkTapped: () { + Navigator.of(context).pop(); + }, + ); + return; + } + if (!provider.isDescriptionValid()) { + Utils.showErrorDialog( + context: context, + message: LocaleKeys.pleaseEnterDescription.tr(), + onOkTapped: () { + Navigator.of(context).pop(); + }, + ); + return; + } + Map? ticketInfo = await provider.submitRequest(context); + if (ticketInfo != null) { + String ticketNumber = ticketInfo['ticketNumber'] ?? ''; + String ticketId = ticketInfo['id'] ?? ''; + String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber\n${LocaleKeys.ticketId.tr()}: $ticketId'; + Utils.showErrorDialog( + context: context, + message: successMessage, + onOkTapped: () { + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); + }, + onCloseTap: () { + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); + }, + ); + } + }).insideContainer, + ], + ); + }, + ), + ); + } + + String? _getProjectDisplayName(EtqanGetProjectsResponse? project) { + if (project == null) return null; + return AppState().isArabic(context) ? project.projectAr : project.projectEn; + } + + void _showProjectSelectionSheet(BuildContext context, EtqanOvrProviderModel provider) { + if (provider.getEtqanProjectsList == null || provider.getEtqanProjectsList!.isEmpty) { + return; + } + showMyBottomSheet( + context, + callBackFunc: () {}, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text(LocaleKeys.selectProject.tr(), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xff2B353E))), + ), + const Divider(), + ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: provider.getEtqanProjectsList!.length, + separatorBuilder: (BuildContext context, int index) => const Divider(height: 1), + itemBuilder: (BuildContext context, int index) { + EtqanGetProjectsResponse project = provider.getEtqanProjectsList![index]; + bool isSelected = provider.selectedProject?.projectId == project.projectId; + return ListTile( + title: Text( + AppState().isArabic(context) ? (project.projectAr ?? '') : (project.projectEn ?? ''), + style: TextStyle(fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, color: isSelected ? MyColors.gradiantEndColor : const Color(0xff2B353E)), + ), + trailing: isSelected ? const Icon(Icons.check, color: MyColors.gradiantEndColor) : null, + onTap: () { + provider.setSelectedProject(project); + Navigator.pop(context); + }, + ); + }, + ), + 16.height, + ], + ), + ); + } + + Widget _buildDropdownField({required String label, required String hint, String? value, required VoidCallback onTap, bool isMandatory = false}) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(15), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)), + if (isMandatory) const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)), + ], + ), + const SizedBox(height: 4), + Text(value ?? hint, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: value != null ? const Color(0xff2B353E) : const Color(0xff575757), letterSpacing: -0.56)), + ], + ), + ), + const Icon(Icons.keyboard_arrow_down, color: Color(0xff575757)), + ], + ), + ), + ); + } + + Widget _buildAttachmentSection(EtqanOvrProviderModel provider) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(LocaleKeys.supportingDocument.tr(), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2B353E))), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(LocaleKeys.attachments.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E))), + if (provider.attachment == null) + InkWell( + onTap: () { + ImageOptions.showImageOptionsNew(context, true, (String base64, File file) { + provider.setAttachment(file, base64); + Navigator.of(context).pop(); + }); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration(color: MyColors.gradiantEndColor, borderRadius: BorderRadius.circular(8)), + child: Text(LocaleKeys.add.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), + ), + ), + ], + ), + if (provider.attachment != null) ...[ + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + const Icon(Icons.attach_file, size: 18, color: Color(0xff575757)), + const SizedBox(width: 8), + Expanded(child: Text(provider.attachment!.path.split('/').last, style: const TextStyle(fontSize: 12, color: Color(0xff575757)), overflow: TextOverflow.ellipsis)), + InkWell( + onTap: () { + provider.removeAttachment(); + }, + child: const Icon(Icons.close, size: 18, color: Colors.red), + ), + ], + ), + ), + ], + ], + ), + ); + } + + Widget _buildDescriptionField(EtqanOvrProviderModel provider) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text(LocaleKeys.description.tr(), style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)), + const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)), + ], + ), + GestureDetector( + onTap: + provider.speechEnabled + ? () { + String localeId = AppState().isArabic(context) ? 'ar_SA' : 'en_US'; + provider.toggleListening(localeId, _descriptionController); + } + : null, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration(color: provider.isListening ? MyColors.gradiantEndColor.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(20)), + child: Icon( + provider.isListening ? Icons.mic : Icons.mic_none, + color: provider.isListening ? MyColors.gradiantEndColor : (provider.speechEnabled ? MyColors.grey57Color : Colors.grey.shade300), + size: 24, + ), + ), + ), + ], + ), + if (provider.isListening) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Container(width: 8, height: 8, decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(4))), + const SizedBox(width: 8), + const Text('Listening...', style: TextStyle(fontSize: 12, color: MyColors.gradiantEndColor, fontWeight: FontWeight.w500)), + ], + ), + ), + TextField( + controller: _descriptionController, + maxLines: 4, + onChanged: (String value) => provider.setDescription(value), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff2B353E), letterSpacing: -0.44), + decoration: InputDecoration( + isDense: true, + hintText: LocaleKeys.writeAMessage.tr(), + hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff575757), letterSpacing: -0.56), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ], + ), + ); + } + + Widget _buildAnonymousCheckbox(EtqanOvrProviderModel provider) { + return InkWell( + onTap: () { + provider.setIsAnonymous(!provider.isAnonymous); + }, + child: Row( + children: [ + Checkbox( + value: provider.isAnonymous, + onChanged: (bool? value) { + if (value != null) { + provider.setIsAnonymous(value); + } + }, + activeColor: MyColors.gradiantEndColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + + Expanded(child: Text(LocaleKeys.submitAsAnonymous.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E), letterSpacing: -0.44))), + ], + ), + ); + } +} diff --git a/lib/ui/etqan_ovr/etqan_ovr_home.dart b/lib/ui/etqan_ovr/etqan_ovr_home.dart new file mode 100644 index 0000000..b55b4c0 --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_ovr_home.dart @@ -0,0 +1,165 @@ +import 'package:easy_localization/easy_localization.dart' as lclize; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.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/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:provider/provider.dart'; +import 'package:intl/src/intl/text_direction.dart' as intl; +import 'package:sizer/sizer.dart'; + +class EtqanOvrHome extends StatefulWidget { + const EtqanOvrHome({Key? key}) : super(key: key); + + @override + _EtqanOvrHomeState createState() => _EtqanOvrHomeState(); +} + +class _EtqanOvrHomeState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().fetchEtqanEmployeeRequests(context); + }); + } + + Color _getStatusColor(String status) { + String statusLower = status.toLowerCase(); + if (statusLower.contains('open') || statusLower.contains('مفتوح')) { + return const Color(0xff2196F3); // Blue for open + } else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) { + return const Color(0xffFF9800); // Orange for in progress + } else if (statusLower.contains('closed') || statusLower.contains('مغلق')) { + return const Color(0xff4CAF50); // Green for closed + } + return MyColors.grey70Color; // Default gray + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.myIncidents.tr()), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + return Column( + children: [ + (provider.getEtqanEmployeeRequestsList == null || provider.getEtqanEmployeeRequestsList!.isEmpty + ? Utils.getNoDataWidget(context) + : ListView.separated( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(20), + itemBuilder: (BuildContext context, int index) { + EtqanGetEmployeeOvrRequestsResponse data = provider.getEtqanEmployeeRequestsList![index]; + return InkWell( + onTap: () { + context.read().setTicketId(data.ticketNumber ?? '', status: data.status, statusAr: data.statusAr, isAnonymous: data.isAnonymous); + Navigator.pushNamed(context, AppRoutes.etqanGetIncidientRequest); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + if (data.status != null && data.status!.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _getStatusColor(data.status ?? "").withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: _getStatusColor(data.status ?? "").withOpacity(0.3), width: 1), + ), + child: (AppState().isArabic(context) ? (data.statusAr ?? "") : (data.status ?? "")).toText11( + color: _getStatusColor(data.status ?? ""), + isBold: true, + ), + ), + if (data.isAnonymous == true) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: MyColors.grey70Color.withOpacity(0.15), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: MyColors.grey70Color.withOpacity(0.3), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.visibility_off, size: 12, color: MyColors.grey70Color), + const SizedBox(width: 4), + (AppState().isArabic(context) ? "مجهول" : "Anonymous").toText11(color: MyColors.grey70Color, isBold: true), + ], + ), + ), + ], + ), + ), + const SizedBox(width: 8), + ], + ), + 8.height, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + (data.ticketNumber ?? "").toText14(isBold: true, color: MyColors.darkTextColor), + Directionality(textDirection: TextDirection.ltr, child: (data.createdDate!.split(" ").first ?? "").toText12(color: MyColors.grey70Color)), + ], + ), + 8.height, + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [(data.description ?? "").toText12(color: MyColors.grey57Color).expanded]), + 3.height, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + RotatedBox( + quarterTurns: AppState().isArabic(context) ? 2 : 0, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.grey70Color, width: 16, height: 16), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) => 12.height, + itemCount: provider.getEtqanEmployeeRequestsList!.length, + )) + .expanded, + DefaultButton(LocaleKeys.reportIncident.tr(), () async { + await Navigator.pushNamed(context, AppRoutes.etqanCreateRequest); + // getOpenTickets(); + }).insideContainer, + ], + ); + }, + ), + ); + } +} diff --git a/lib/ui/etqan_ovr/etqan_request_detailed.dart b/lib/ui/etqan_ovr/etqan_request_detailed.dart new file mode 100644 index 0000000..41d11c6 --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_request_detailed.dart @@ -0,0 +1,235 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.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/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:path_provider/path_provider.dart'; + +class EtqanOvrRequestDetailed extends StatefulWidget { + const EtqanOvrRequestDetailed({Key? key}) : super(key: key); + + @override + _EtqanOvrRequestDetailedState createState() => _EtqanOvrRequestDetailedState(); +} + +class _EtqanOvrRequestDetailedState extends State { + List get fieldOrder => ['Status', 'Project', 'Created', 'Is Anonymous', 'Occurrence Time', 'OVR Reference No', 'Involved Employee Number', 'Description']; + + List get hiddenFields => ['OVR Id']; + + Color _getStatusColor(String status) { + String statusLower = status.toLowerCase(); + if (statusLower.contains('open') || statusLower.contains('مفتوح')) { + return const Color(0xff2196F3); // Blue for open + } else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) { + return const Color(0xffFF9800); // Orange for in progress + } else if (statusLower.contains('closed') || statusLower.contains('مغلق')) { + return const Color(0xff4CAF50); // Green for closed + } + return MyColors.grey70Color; // Default gray + } + + Future _viewAttachment(BuildContext context, String base64Data) async { + try { + Uint8List bytes = base64.decode(base64Data); + String dir = (await getApplicationDocumentsDirectory()).path; + String ext = 'pdf'; + if (base64Data.startsWith('/9j/')) { + ext = 'jpg'; + } else if (base64Data.startsWith('iVBORw0KGgo')) { + ext = 'png'; + } + File file = File("$dir/${DateTime.now().millisecondsSinceEpoch}.$ext"); + await file.writeAsBytes(bytes); + OpenFilex.open(file.path); + } catch (e) { + Utils.showToast("Cannot open attachment."); + } + } + + List _getOrderedList(List list, EtqanOvrProviderModel provider) { + List orderedList = []; + + List filteredList = list.where((EtqanGetIncidentRequestResponse item) => !hiddenFields.any((f) => f.toLowerCase() == item.key?.toLowerCase())).toList(); + + for (String fieldName in fieldOrder) { + // Check if this is Status or Is Anonymous - inject from provider + if (fieldName.toLowerCase() == 'status' && provider.getCurrentStatus.isNotEmpty) { + orderedList.add(EtqanGetIncidentRequestResponse( + key: 'Status', + keyAr: 'الحالة', + value: provider.getCurrentStatus, + valueAr: provider.getCurrentStatusAr.isNotEmpty ? provider.getCurrentStatusAr : provider.getCurrentStatus, + )); + } else if (fieldName.toLowerCase() == 'is anonymous') { + orderedList.add(EtqanGetIncidentRequestResponse( + key: 'Is Anonymous', + keyAr: 'مجهول', + value: provider.getCurrentIsAnonymous.toString(), + valueAr: provider.getCurrentIsAnonymous.toString(), + )); + } else { + // Regular field from API + var item = filteredList.firstWhere((e) => e.key?.toLowerCase() == fieldName.toLowerCase(), orElse: () => EtqanGetIncidentRequestResponse()); + if (item.key != null) { + orderedList.add(item); + } + } + } + + for (var item in filteredList) { + if (!fieldOrder.any((f) => f.toLowerCase() == item.key?.toLowerCase())) { + orderedList.add(item); + } + } + + return orderedList; + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + EtqanOvrProviderModel provider = context.read(); + provider.fetchEtqanEmployeeIncidentRequests(context, provider.getTicketId); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.incidentDetails.tr()), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + if (provider.getEtqanEmployeeIncidnetRequest == null || provider.getEtqanEmployeeIncidnetRequest!.isEmpty) { + return Utils.getNoDataWidget(context); + } + + List orderedList = _getOrderedList(provider.getEtqanEmployeeIncidnetRequest!, provider); + + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(16), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: const Color(0xffefefef), width: 1), + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2))], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + orderedList.asMap().entries.map((MapEntry entry) { + int index = entry.key; + EtqanGetIncidentRequestResponse data = entry.value; + bool isLast = index == orderedList.length - 1; + bool isAttachment = data.key?.toLowerCase() == 'attachment'; + bool isStatus = data.key?.toLowerCase() == 'status'; + bool isAnonymous = data.key?.toLowerCase() == 'is anonymous'; + + String displayKey = context.locale.languageCode == "ar" ? (data.keyAr ?? data.key ?? "") : (data.key ?? ""); + String displayValue = context.locale.languageCode == "ar" ? (data.valueAr ?? data.value ?? "") : (data.value ?? ""); + + bool hasSubValues = data.subValues != null && data.subValues!.isNotEmpty; + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: displayKey.toText12(isBold: true, color: MyColors.grey57Color)), + 8.width, + Expanded( + flex: 3, + child: isStatus + ? displayValue.toText14( + color: _getStatusColor(displayValue), + isBold: true, + ) + : isAnonymous + ? (displayValue.toLowerCase() == 'true' || displayValue.toLowerCase() == 'yes' || displayValue == '1' + ? (context.locale.languageCode == "ar" ? "نعم" : "Yes") + : (context.locale.languageCode == "ar" ? "لا" : "No") + ).toText14(color: MyColors.darkTextColor) + : isAttachment && hasSubValues + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: data.subValues!.asMap().entries.map((entry) { + int subIndex = entry.key; + String base64Data = entry.value; + return Padding( + padding: EdgeInsets.only(bottom: subIndex < data.subValues!.length - 1 ? 8.0 : 0), + child: GestureDetector( + onTap: () => _viewAttachment(context, base64Data), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: context.locale.languageCode == "ar" ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Text( + data.subValues!.length > 1 ? '${LocaleKeys.viewAttachment.tr()} ${subIndex + 1}' : LocaleKeys.viewAttachment.tr(), + style: const TextStyle( + fontSize: 14, + color: MyColors.green9CColor, + decoration: TextDecoration.underline, + decorationColor: MyColors.green9CColor, + ), + ), + ], + ), + ), + ); + }).toList(), + ) + : hasSubValues + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (displayValue.isNotEmpty) displayValue.toText14(color: MyColors.darkTextColor), + if (displayValue.isNotEmpty) 8.height, + ...data.subValues! + .map( + (subValue) => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [const Text("• ", style: TextStyle(fontSize: 14)), Expanded(child: subValue.toText14(color: MyColors.darkTextColor))], + ), + ), + ) + .toList(), + ], + ) + : (displayValue.isNotEmpty ? displayValue : "-").toText14(color: MyColors.darkTextColor), + ), + ], + ), + ), + if (!isLast) const Padding(padding: EdgeInsets.only(left: 14, right: 14), child: Divider(height: 1, thickness: 1, color: Color(0xffefefef))), + ], + ); + }).toList(), + ), + ), + ); + }, + ).paddingOnly(bottom: 20), + ); + } +} diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 3122d17..deb3131 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -167,8 +167,9 @@ class _DashboardScreenState extends State with WidgetsBindingOb // await data.fetchTicketBooking(); // if (data.ticketBookingResponse != null && !data.ticketBookingResponse!.success) { - // // data.fetchTicketBalance(); + // data.fetchTicketBalance(); // } + //continue here } diff --git a/lib/ui/landing/widget/menus_widget.dart b/lib/ui/landing/widget/menus_widget.dart index be04937..e764dca 100644 --- a/lib/ui/landing/widget/menus_widget.dart +++ b/lib/ui/landing/widget/menus_widget.dart @@ -122,30 +122,28 @@ class MenusWidget extends StatelessWidget { ], ).paddingOnly(left: 10, right: 10, bottom: 6, top: 6), ).onPress(() async { + Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); + + // var pro = Provider.of(context, listen: false); // - // // Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => TicketDetailedScreen(url: url, jwtToken: ssoToken.data!.accessToken))); + // // if (menuEntry.menuName == "HMG_TICKET_REQUESTS") { + // Utils.showLoading(context); + // //Ticket Work + // if (pro.ticketBookingResponse != null && pro.ticketBookingResponse!.success) { + // SSOAuthModel? ssoToken = await pro.fetchSSOAuthRedirection(clientID: pro.ticketBookingResponse!.clientId); + // if (ssoToken != null) { + // dynamic url = await pro.fetchURLRedirection(token: ssoToken.data!.accessToken!); + // await pro.fetchTicketAccuralBalance(context, DateTime.now()); + // pro.ticketHistoryTransactionList = await EITApiClient().getEITTransactions("HMG_TICKET_ITENARY_HR_EIT_SS", isCompleteList: true); // - // Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); - var pro = Provider.of(context, listen: false); - - // if (menuEntry.menuName == "HMG_TICKET_REQUESTS") { - Utils.showLoading(context); - //Ticket Work - if (pro.ticketBookingResponse != null && pro.ticketBookingResponse!.success) { - SSOAuthModel? ssoToken = await pro.fetchSSOAuthRedirection(clientID: pro.ticketBookingResponse!.clientId); - if (ssoToken != null) { - dynamic url = await pro.fetchURLRedirection(token: ssoToken.data!.accessToken!); - await pro.fetchTicketAccuralBalance(context, DateTime.now()); - pro.ticketHistoryTransactionList = await EITApiClient().getEITTransactions("HMG_TICKET_ITENARY_HR_EIT_SS", isCompleteList: true); - - Utils.hideLoading(context); - // Here Need Work - Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => TicketDetailedScreen(url: url, jwtToken: ssoToken.data!.accessToken))); - } - //} - } else { - Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); - } + // Utils.hideLoading(context); + // // Here Need Work + // Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => TicketDetailedScreen(url: url, jwtToken: ssoToken.data!.accessToken))); + // } + // //} + // } else { + // Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); + // } }), ], ); diff --git a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart index 61681eb..9d7e94a 100644 --- a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart +++ b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart @@ -168,8 +168,8 @@ class ApprovalLevelfragment extends StatelessWidget { if (wFHistory[index].date!.isEmpty || wFHistory[index].action!.toLowerCase() == "NO ACTION" || wFHistory[index + 1].date! == "") { return ""; } else { - DateTime dateTimeTo = DateUtil.convertSimpleStringDateToDate(wFHistory[index].date!); - DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(wFHistory[index + 1].date!); + DateTime dateTimeTo = DateUtil.convertSimpleStringDateToDate(wFHistory[index].date!, isITG: true); + DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(wFHistory[index + 1].date!, isITG: true); Duration duration = dateTimeTo.difference(dateTimeFrom); return "Action duration: " + DateUtil.formatDuration(duration); } diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index a5d346f..984d6ad 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -141,7 +141,7 @@ void galleryImageAndroid(Function(String, File) image) async { } void cameraImageAndroid(Function(String, File) image) async { - File _image = File(( await picker.pickMedia())?.path ?? ""); + File _image = File((await picker.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -159,7 +159,7 @@ class _BottomSheet extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 12.0), - decoration: BoxDecoration( borderRadius: const BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))), + decoration: const BoxDecoration( borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))), child: SafeArea( top: false, child: Column( diff --git a/pubspec.yaml b/pubspec.yaml index 6abe6fc..49dec60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -#version: 3.7.1+300081 -version: 3.9.3+1 +version: 3.7.1+300081 +#version: 3.9.3+1 environment: sdk: ^3.7.0 @@ -102,6 +102,9 @@ dependencies: audio_waveforms: ^1.3.0 rxdart: ^0.27.7 + #Speech to Text + speech_to_text: ^7.0.0 + #Encryption flutter_des: ^2.1.0