From 2c7d3bf963546404b1766da9632e0a66174ea723 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 26 Feb 2026 14:51:56 +0300 Subject: [PATCH] QIPS-1175,QIPS-1179,QIPS-1180 --- assets/langs/ar-SA.json | 6 +- assets/langs/en-US.json | 6 +- lib/classes/utils.dart | 4 +- lib/generated/codegen_loader.g.dart | 12 ++- lib/generated/locale_keys.g.dart | 4 + .../etqan_get_employee_incident_report.dart | 12 +++ lib/provider/etqan_ovr_provider.dart | 13 ++- lib/ui/etqan_ovr/etqan_create_request.dart | 8 +- lib/ui/etqan_ovr/etqan_ovr_home.dart | 6 +- lib/ui/etqan_ovr/etqan_request_detailed.dart | 97 +++++++++++++++++-- 10 files changed, 150 insertions(+), 18 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 64708d4..88a3891 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -650,7 +650,11 @@ "selectProject": "حدد المشروع", "project": "مشروع", "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", "ticketNumber": "رقم التذكرة", "ticketId": "معرف التذكرة", - "pleaseSelectProject": "الرجاء اختيار مشروع" + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment" : "عرض المرفق" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b130204..4e29cbc 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -648,7 +648,11 @@ "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" + "pleaseSelectProject": "Please select a project", + "viewAttachment" : "View Attachment" } \ No newline at end of file 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/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 9f72052..1a3ec71 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -663,9 +663,13 @@ class CodegenLoader extends AssetLoader{ "selectProject": "حدد المشروع", "project": "مشروع", "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", "ticketNumber": "رقم التذكرة", "ticketId": "معرف التذكرة", - "pleaseSelectProject": "الرجاء اختيار مشروع" + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment": "عرض المرفق" }; static const Map _en_US = { "mohemm": "Mohemm", @@ -1316,9 +1320,13 @@ static const Map _en_US = { "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" + "pleaseSelectProject": "Please select a project", + "viewAttachment": "View Attachment" }; 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 6bcc80b..d060682 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -635,8 +635,12 @@ abstract class LocaleKeys { 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'; } diff --git a/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart index 3743cf3..e4a57fd 100644 --- a/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart +++ b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart @@ -22,11 +22,17 @@ class EtqanGetEmployeeIncidentRequestModel { 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)); @@ -35,11 +41,17 @@ class EtqanGetIncidentRequestResponse { 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/provider/etqan_ovr_provider.dart b/lib/provider/etqan_ovr_provider.dart index b717b0f..1f6ab80 100644 --- a/lib/provider/etqan_ovr_provider.dart +++ b/lib/provider/etqan_ovr_provider.dart @@ -47,7 +47,15 @@ class EtqanOvrProviderModel with ChangeNotifier, DiagnosticableTreeMixin { /// Initialize speech recognition - call this once when screen loads Future initSpeech() async { - _speechEnabled = await _speechToText.initialize(); + _speechEnabled = await _speechToText.initialize( + onStatus: (String status) { + // Update listening state when speech recognition status changes + if (status == 'done' || status == 'notListening') { + _isListening = false; + notifyListeners(); + } + }, + ); notifyListeners(); } @@ -61,6 +69,9 @@ class EtqanOvrProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); }, localeId: localeId, + listenFor: const Duration(seconds: 30), + pauseFor: const Duration(seconds: 3), + listenMode: ListenMode.confirmation, ); _isListening = true; notifyListeners(); diff --git a/lib/ui/etqan_ovr/etqan_create_request.dart b/lib/ui/etqan_ovr/etqan_create_request.dart index 1618711..ce636ff 100644 --- a/lib/ui/etqan_ovr/etqan_create_request.dart +++ b/lib/ui/etqan_ovr/etqan_create_request.dart @@ -4,6 +4,7 @@ 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'; @@ -46,7 +47,7 @@ class _EtqanOvrCreateRequestState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, - appBar: AppBarWidget(context, title: LocaleKeys.createRequest.tr(), showHomeButton: false), + appBar: AppBarWidget(context, title: LocaleKeys.reportIncident.tr(), showHomeButton: false), body: Consumer( builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { return Column( @@ -112,7 +113,10 @@ class _EtqanOvrCreateRequestState extends State { context: context, message: successMessage, onOkTapped: () { - Navigator.of(context).pop(); + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); + }, + onCloseTap: () { + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); }, ); } diff --git a/lib/ui/etqan_ovr/etqan_ovr_home.dart b/lib/ui/etqan_ovr/etqan_ovr_home.dart index 7a48a51..c4174a1 100644 --- a/lib/ui/etqan_ovr/etqan_ovr_home.dart +++ b/lib/ui/etqan_ovr/etqan_ovr_home.dart @@ -4,7 +4,7 @@ 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/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'; @@ -36,7 +36,7 @@ class _EtqanOvrHomeState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, - appBar: AppBarWidget(context, title: LocaleKeys.etqanRequest.tr()), + appBar: AppBarWidget(context, title: LocaleKeys.myIncidents.tr()), body: Consumer( builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { return Column( @@ -93,7 +93,7 @@ class _EtqanOvrHomeState extends State { itemCount: provider.getEtqanEmployeeRequestsList!.length, )) .expanded, - DefaultButton(LocaleKeys.createRequest.tr(), () async { + 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 index b1ed12a..6f9aebc 100644 --- a/lib/ui/etqan_ovr/etqan_request_detailed.dart +++ b/lib/ui/etqan_ovr/etqan_request_detailed.dart @@ -1,3 +1,7 @@ +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/classes/colors.dart'; @@ -10,6 +14,8 @@ 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); @@ -23,6 +29,24 @@ class _EtqanOvrRequestDetailedState extends State { List get hiddenFields => ['OVR Id', 'Is Anonymous']; + 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) { List orderedList = []; @@ -48,7 +72,7 @@ class _EtqanOvrRequestDetailedState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - var provider = context.read(); + EtqanOvrProviderModel provider = context.read(); provider.fetchEtqanEmployeeIncidentRequests(context, provider.getTicketId); }); } @@ -57,7 +81,7 @@ class _EtqanOvrRequestDetailedState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, - appBar: AppBarWidget(context, title: LocaleKeys.etqanRequest.tr()), + appBar: AppBarWidget(context, title: LocaleKeys.incidentDetails.tr()), body: Consumer( builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { if (provider.getEtqanEmployeeIncidnetRequest == null || provider.getEtqanEmployeeIncidnetRequest!.isEmpty) { @@ -74,7 +98,7 @@ class _EtqanOvrRequestDetailedState extends State { color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xffefefef), width: 1), - boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2))], + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -83,6 +107,13 @@ class _EtqanOvrRequestDetailedState extends State { int index = entry.key; EtqanGetIncidentRequestResponse data = entry.value; bool isLast = index == orderedList.length - 1; + bool isAttachment = data.key?.toLowerCase() == 'attachment'; + + 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( @@ -90,9 +121,63 @@ class _EtqanOvrRequestDetailedState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(flex: 2, child: (data.key ?? "").toText12(isBold: true, color: MyColors.grey57Color)), + Expanded(flex: 2, child: displayKey.toText12(isBold: true, color: MyColors.grey57Color)), 8.width, - Expanded(flex: 3, child: (data.value ?? "-").toText14(color: MyColors.darkTextColor)), + Expanded( + flex: 3, + child: + 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(), + // textAlign: context.locale.languageCode == "ar" ? TextAlign.start : TextAlign.start, + 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), + ), ], ), ), @@ -104,7 +189,7 @@ class _EtqanOvrRequestDetailedState extends State { ), ); }, - ), + ).paddingOnly(bottom: 20), ); } }