diff --git a/assets/images/attachment_icon.svg b/assets/images/attachment_icon.svg new file mode 100644 index 00000000..d0da9eaf --- /dev/null +++ b/assets/images/attachment_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/chat_attachment.svg b/assets/images/chat_attachment.svg new file mode 100644 index 00000000..80d2003b --- /dev/null +++ b/assets/images/chat_attachment.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/chat_delivered.svg b/assets/images/chat_delivered.svg new file mode 100644 index 00000000..4ac9df87 --- /dev/null +++ b/assets/images/chat_delivered.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/chat_mic.svg b/assets/images/chat_mic.svg new file mode 100644 index 00000000..60b5d00f --- /dev/null +++ b/assets/images/chat_mic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/chat_msg_send.svg b/assets/images/chat_msg_send.svg new file mode 100644 index 00000000..e0ad1ebc --- /dev/null +++ b/assets/images/chat_msg_send.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/chat_seen.svg b/assets/images/chat_seen.svg new file mode 100644 index 00000000..f6aa3475 --- /dev/null +++ b/assets/images/chat_seen.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/chat_sent.svg b/assets/images/chat_sent.svg new file mode 100644 index 00000000..0af44564 --- /dev/null +++ b/assets/images/chat_sent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/star_empty.svg b/assets/images/star_empty.svg new file mode 100644 index 00000000..03cb916c --- /dev/null +++ b/assets/images/star_empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/star_filled.svg b/assets/images/star_filled.svg new file mode 100644 index 00000000..16abaa52 --- /dev/null +++ b/assets/images/star_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart index 20c55464..9594acd0 100644 --- a/lib/controllers/api_routes/api_manager.dart +++ b/lib/controllers/api_routes/api_manager.dart @@ -42,7 +42,9 @@ class ApiManager { if (jsonDecode(response.body) is Map) { final message = jsonDecode(response.body)["message"]; if (message != null && message.toString().isNotEmpty) { - Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); + if (enableToastMessage) { + Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); + } } } } diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index e283defd..2e7e5f56 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -4,18 +4,24 @@ class URLs { static const String appReleaseBuildNumber = "28"; // static const host1 = "https://atomsm.hmg.com"; // production url - static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url + static const host1 = "https://atomsmuat.hmg.com"; // local UAT url // static const host1 = "http://10.201.111.125:9495"; // temporary Server UAT url - // static String _baseUrl = "$_host/mobile"; + static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/v4/mobile"; // for asset inventory on UAT // static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation + static const String chatHubUrl = "https://apiderichat.hmg.com/chathub"; + + static const String chatHubUrlApi = "$chatHubUrl/api"; // new V2 apis + static const String chatHubUrlChat = "$chatHubUrl/hubs/chat"; // new V2 apis + static const String chatApiKey = "f53a98286f82798d588f67a7f0db19f7aebc839e"; // new V2 apis + static String _host = host1; set host(String value) => _host = value; @@ -171,8 +177,6 @@ class URLs { static get taskJobImpactStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=1264"; - static get getLoanPeriod => "$_baseUrl/Lookups/GetLookup?lookupEnum=3051"; - //service request..... static get getServiceRequests => "$_baseUrl/CallRequest/GetCallRequests"; // get static get getServiceRequestById => "$_baseUrl/CallRequest/GetCallRequestById"; // get @@ -311,6 +315,7 @@ class URLs { static get getPentry => "$_baseUrl/return/pentry/details"; // get static get updatePentry => "$_baseUrl/Visit/UpdateVisit"; // get static get getPentryTaskStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=403"; // get + // get static get getPentryVisitStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=402"; // get static get getPentryStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=401"; // get // contacts @@ -346,7 +351,22 @@ class URLs { static get getClassification => '$_baseUrl/AssetInventory/Classification'; - //Loan Module... - //getDepartmentBasedOnSite - static get getMedDepartmentBasedOnSite => '$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite'; + //internal Audit + static get getInternalAuditEquipmentById => "$_baseUrl/InternalAuditEquipments/GetInternalAuditEquipmentById"; + static get getInternalAuditSystemById => "$_baseUrl/InternalAuditSystems/GetInternalAuditSystemByIdV2"; + static get getInternalAuditChecklist => "$_baseUrl/Lookups/GetLookup?lookupEnum=43"; + static get getInternalAuditWoType => "$_baseUrl/Lookups/GetLookup?lookupEnum=2500"; + static get getInternalAuditFindingType => "$_baseUrl/Lookups/GetLookup?lookupEnum=2502"; + static get addOrUpdateEquipmentInternalAudit => "$_baseUrl/InternalAuditEquipments/AddOrUpdateAuditEquipment"; + static get addOrUpdateInternalAuditSystem => "$_baseUrl/InternalAuditSystems/AddOrUpdateInternalAuditSystem"; + static get getWoAutoComplete => "$_baseUrl/InternalAuditSystems/AutoCompleteAllWorkOrder"; + static get updateAuditEquipmentsEngineer => "$_baseUrl/InternalAuditEquipments/UpdateAuditEquipmentsEngineer"; + static get updateAuditSystemEngineer => "$_baseUrl/InternalAuditSystems/UpdateAuditSystemEngineer"; + static get loadAllWorkOrderDetailsByID => "$_baseUrl/InternalAuditSystems/LoadAllWorkOrderDetailsByID"; + + //chat + static get chatSdkToken => '$chatHubUrlApi/auth/sdk-token'; + //survey + static get getQuestionnaire => '$_baseUrl/SurveyQuestionnaire/GetQuestionnaire'; + static get submitSurvey => '$_baseUrl/SurveyQuestionnaire/SubmitSurvey'; } diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart index 16abd263..7a8a67f9 100644 --- a/lib/controllers/notification/firebase_notification_manger.dart +++ b/lib/controllers/notification/firebase_notification_manger.dart @@ -9,6 +9,7 @@ import 'package:test_sa/controllers/notification/notification_manger.dart'; import 'package:test_sa/models/device/device_transfer.dart'; import 'package:test_sa/models/new_models/gas_refill_model.dart'; import 'package:test_sa/modules/cm_module/views/service_request_detail_main_view.dart'; +import 'package:test_sa/modules/cx_module/survey/survey_page.dart'; import 'package:test_sa/modules/pm_module/ppm_wo/ppm_details_page.dart'; import 'package:test_sa/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart'; import 'package:test_sa/views/pages/device_transfer/device_transfer_details.dart'; @@ -120,6 +121,9 @@ class FirebaseNotificationManger { break; case "12": serviceClass = RecurrentWorkOrderView(taskId: int.parse(messageData["requestNumber"].toString())); + case "17": + serviceClass = SurveyPage(surveyId: int.parse(messageData["requestNumber"].toString())); + //Didn't handle task request yet... // case 6: // serviceClass = TaskRequestDetailsView( diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 470f803d..99f77a02 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -133,15 +133,24 @@ class AllRequestsProvider extends ChangeNotifier { List getStatues(BuildContext context) { List list = [1, 2, 3, 4]; + //TODO need to refactor this code .... if (context.userProvider.isAssessor) { list = [9]; return list; } + if (context.userProvider.isQualityUser) { + + list = [10,11]; + return list; + } if (!context.userProvider.isNurse) { list.add(5); } + if (context.userProvider.isEngineer) { + list.add(10); + } list.add(6); // task module if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { diff --git a/lib/controllers/providers/api/gas_refill_provider.dart b/lib/controllers/providers/api/gas_refill_provider.dart index 40d6327a..8b522790 100644 --- a/lib/controllers/providers/api/gas_refill_provider.dart +++ b/lib/controllers/providers/api/gas_refill_provider.dart @@ -47,12 +47,16 @@ class GasRefillProvider extends ChangeNotifier { // failed _loading = false bool isLoading = false; + GasRefillModel? gasRefillModel; + Future getGasRefillObjectById(num id) async { try { + gasRefillModel = null; Response response = await ApiManager.instance.get(URLs.getGasRefillById + "?gasRefillId=$id"); - if (response.statusCode >= 200 && response.statusCode < 300) { - return GasRefillModel.fromJson(json.decode(response.body)["data"]); + gasRefillModel = GasRefillModel.fromJson(json.decode(response.body)["data"]); + notifyListeners(); + return gasRefillModel; } else { return null; } diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index 481d06d5..471be087 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -45,6 +45,7 @@ class UserProvider extends ChangeNotifier { bool get isNurse => user!.type == UsersTypes.normal_user; bool get isAssessor => user!.type == UsersTypes.assessor || user!.type == UsersTypes.assessorTl; + bool get isQualityUser => user!.type == UsersTypes.qualityUser; VerifyOtpModel _verifyOtpModel = VerifyOtpModel(); SwipeTransaction _swipeTransactionModel = SwipeTransaction(); diff --git a/lib/dashboard_latest/widgets/request_category_list.dart b/lib/dashboard_latest/widgets/request_category_list.dart index 07d5d92d..9ca9daff 100644 --- a/lib/dashboard_latest/widgets/request_category_list.dart +++ b/lib/dashboard_latest/widgets/request_category_list.dart @@ -3,6 +3,7 @@ import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -56,6 +57,11 @@ class RequestCategoryList extends StatelessWidget { return InventorySessionItemView(requestData: request); case 9: return TrafRequestItemView(requestData: request); + //TODO need to verify this ... + case 10: + return EquipmentInternalAuditItemView(requestData: request); + case 11: + return EquipmentInternalAuditItemView(requestData: request); default: return Container( height: 100, diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index 85299f73..f85af74c 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -19,6 +19,7 @@ extension BuildContextExtension on BuildContext { SettingProvider get settingProvider => Provider.of(this, listen: false); UserProvider get userProvider => Provider.of(this, listen: false); + bool isTablet() { final mediaQueryData = MediaQuery.of(this); final double screenWidth = mediaQueryData.size.width; @@ -27,9 +28,12 @@ extension BuildContextExtension on BuildContext { bool isTablet = (devicePixelRatio < 2 && (screenWidth >= 700 || screenHeight >= 700)) || (devicePixelRatio >= 2 && (screenWidth >= 1000 || screenHeight >= 1000)); return isTablet; } - void showConfirmDialog(String message, {String? title, VoidCallback? onTap}) => showDialog( + + + + void showConfirmDialog(String message, {String? title, VoidCallback? onTap, String? okTitle}) => showDialog( context: this, - builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap, title: title), + builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap, title: title,okTitle: okTitle), ); Future showBottomSheet(Widget childWidget, {bool? isDismissible, String? title}) => showModalBottomSheet( diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index eabd9fce..855560d1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -6,6 +6,21 @@ extension StringExtensions on String { void get showToast => Fluttertoast.showToast(msg: this); + String get chatMsgTime { + DateTime dateTime = DateTime.parse(this).toLocal(); + return DateFormat('hh:mm a').format(dateTime); + } + + String get chatMsgDate { + DateTime dateTime = DateTime.parse(this).toLocal(); + return DateFormat('EEE dd MMM').format(dateTime); + } + + String get chatMsgDateWithYear { + DateTime dateTime = DateTime.parse(this).toLocal(); + return DateFormat('EEE dd MMM, yyyy').format(dateTime); + } + String get toServiceRequestCardFormat { DateTime dateTime = DateTime.parse(this); return "${DateFormat('dd MMM, yyyy').format(dateTime)}\n${DateFormat('hh:mm a').format(dateTime)}"; diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 8caa8ccf..3fd48cd8 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -79,13 +79,15 @@ extension WidgetExtensions on Widget { : this; } - Widget toShimmer({bool isShow = true, double radius = 20, required BuildContext context}) => isShow + Widget toShimmer({bool isShow = true, double radius = 20, double? width, double? height, required BuildContext context}) => isShow ? Shimmer.fromColors( baseColor: context.isDark ? AppColor.backgroundDark : const Color(0xffe8eff0), highlightColor: AppColor.background(context), child: ClipRRect( borderRadius: BorderRadius.circular(radius), child: Container( + width: width, + height: height, color: AppColor.background(context), child: this, ), diff --git a/lib/helper/utils.dart b/lib/helper/utils.dart index b56c7474..f4db3acd 100644 --- a/lib/helper/utils.dart +++ b/lib/helper/utils.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.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'; @@ -23,6 +24,7 @@ import 'package:nfc_manager/platform_tags.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/views/widgets/dialogs/loading_dialog.dart'; // ignore_for_file: avoid_annotating_with_dynamic @@ -49,19 +51,21 @@ class Utils { return null; } } -static String getOrdinal(int number) { - if (number >= 11 && number <= 13) return "${number}th"; - switch (number % 10) { - case 1: - return "${number}st"; - case 2: - return "${number}nd"; - case 3: - return "${number}rd"; - default: - return "${number}th"; + + static String getOrdinal(int number) { + if (number >= 11 && number <= 13) return "${number}th"; + switch (number % 10) { + case 1: + return "${number}st"; + case 2: + return "${number}nd"; + case 3: + return "${number}rd"; + default: + return "${number}th"; + } } -} + static int stringToHex(String colorCode) { try { return int.parse(colorCode.replaceAll("#", "0xff")); @@ -73,7 +77,8 @@ static String getOrdinal(int number) { static Future delay(int millis) async { return await Future.delayed(Duration(milliseconds: millis)); } - static bool isBeforeOrEqualCurrentTime(TimeOfDay t1, TimeOfDay t2) { + + static bool isBeforeOrEqualCurrentTime(TimeOfDay t1, TimeOfDay t2) { return t1.hour < t2.hour || (t1.hour == t2.hour && t1.minute <= t2.minute); } @@ -102,6 +107,21 @@ static String getOrdinal(int number) { _isLoadingVisible = false; } + static void showUploadingDialog(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _isLoadingVisible = true; + + showDialog( + context: context, + barrierColor: Colors.black.withOpacity(0.5), + useRootNavigator: false, + builder: (BuildContext context) => const UploadingDialog(), + ).then((value) { + _isLoadingVisible = false; + }); + }); + } + static Future getStringFromPrefs(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getString(key) ?? ""; @@ -176,6 +196,7 @@ static String getOrdinal(int number) { ), ); } + // // static Widget getNoDataWidget(BuildContext context) { // return Column( @@ -421,15 +442,15 @@ static String getOrdinal(int number) { } return false; } - // - // static bool isDate(String input, String format) { - // try { - // DateTime d = DateFormat(format).parseStrict(input); - // //print(d); - // return true; - // } catch (e) { - // //print(e); - // return false; - // } - // } +// +// static bool isDate(String input, String format) { +// try { +// DateTime d = DateFormat(format).parseStrict(input); +// //print(d); +// return true; +// } catch (e) { +// //print(e); +// return false; +// } +// } } diff --git a/lib/main.dart b/lib/main.dart index 8b34b73a..29c6f8af 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,13 @@ import 'package:test_sa/controllers/providers/api/status_drop_down/report/servic import 'package:test_sa/modules/asset_inventory_module/provider/asset_inventory_provider.dart'; import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/nurse/create_new_request_view.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart'; import 'package:test_sa/modules/loan_module/pages/create_loan_request_view.dart'; import 'package:test_sa/modules/loan_module/provider/loan_period_provider.dart'; import 'package:test_sa/modules/loan_module/provider/loan_provider.dart'; @@ -103,6 +110,9 @@ import 'controllers/providers/api/gas_refill_comments.dart'; import 'controllers/providers/api/user_provider.dart'; import 'controllers/providers/settings/setting_provider.dart'; import 'dashboard_latest/dashboard_provider.dart'; +import 'modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; +import 'modules/cx_module/survey/survey_provider.dart'; import 'new_views/pages/gas_refill_request_form.dart'; import 'providers/lookups/classification_lookup_provider.dart'; import 'providers/lookups/department_lookup_provider.dart'; @@ -242,8 +252,11 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => ServiceReportRepairLocationProvider()), ChangeNotifierProvider(create: (_) => ServiceRequestFaultDescriptionProvider()), - ///todo deleted - //ChangeNotifierProvider(create: (_) => ServiceReportVisitOperatorProvider()), + //chat + ChangeNotifierProvider(create: (_) => ChatProvider()), + //chat + ChangeNotifierProvider(create: (_) => SurveyProvider()), + ///todo deleted //ChangeNotifierProvider(create: (_) => ServiceReportMaintenanceSituationProvider()), //ChangeNotifierProvider(create: (_) => ServiceReportUsersProvider()), @@ -290,6 +303,10 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => PpmServiceProvider()), ChangeNotifierProvider(create: (_) => CommentsProvider()), ChangeNotifierProvider(create: (_) => GasRefillCommentsProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditCheckListProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditWoTypeProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditFindingTypeProvider()), ///todo deleted //ChangeNotifierProvider(create: (_) => RequestStatusProvider()), @@ -361,6 +378,10 @@ class MyApp extends StatelessWidget { ProfilePage.id: (_) => const ProfilePage(), ReportBugPage.id: (_) => const ReportBugPage(), HelpCenterPage.id: (_) => const HelpCenterPage(), + CreateEquipmentInternalAuditForm.id: (_) => const CreateEquipmentInternalAuditForm(), + CreateSystemInternalAuditForm.id: (_) => const CreateSystemInternalAuditForm(), + UpdateEquipmentInternalAuditPage.id: (_) => UpdateEquipmentInternalAuditPage(), + UpdateSystemInternalAuditPage.id: (_) => UpdateSystemInternalAuditPage(), CreateLoanRequestView.id: (_) => const CreateLoanRequestView(), // SwipeSuccessView.routeName: (_) => const SwipeSuccessView(), // SwipeHistoryView.routeName: (_) => const SwipeHistoryView(), diff --git a/lib/models/enums/user_types.dart b/lib/models/enums/user_types.dart index 63f1d9d7..cd6b5f13 100644 --- a/lib/models/enums/user_types.dart +++ b/lib/models/enums/user_types.dart @@ -3,5 +3,6 @@ enum UsersTypes { normal_user, // 1 nurse, // 1 assessor, - assessorTl + assessorTl, + qualityUser } diff --git a/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart b/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart index dda5432b..21541769 100644 --- a/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart +++ b/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart @@ -31,11 +31,11 @@ class ActivityMaintenanceHelperModel { WorkOrderAssignedEmployee? assignedEmployee; SuppEngineerWorkOrders? supEngineer; ActivityMaintenanceAssistantEmployees? modelAssistantEmployees; + List? assistantEmployList=[]; List? assistantEmployees; List? activityMaintenanceTimers = []; TimerModel? activityMaintenanceTimerModel = TimerModel(); TimerModel? activityTimePicker; - List? assistantEmployList=[]; List? timerModelList = []; ActivityMaintenanceHelperModel( diff --git a/lib/models/new_models/work_order_detail_model.dart b/lib/models/new_models/work_order_detail_model.dart index febd3d80..25519bd6 100644 --- a/lib/models/new_models/work_order_detail_model.dart +++ b/lib/models/new_models/work_order_detail_model.dart @@ -348,33 +348,46 @@ class AssetGroup { } class WorkOrderAssignedEmployee { - WorkOrderAssignedEmployee({ - required this.userId, - required this.userName, - required this.email, - required this.languageId, - }); - - String userId; + String? userId; String? userName; String? email; + String? employeeId; int? languageId; + String? extensionNo; + String? phoneNumber; + String? positionName; + String? position; + bool? isActive; - factory WorkOrderAssignedEmployee.fromJson(Map json) { - return WorkOrderAssignedEmployee( - userId: json["userId"], - userName: json["userName"], - email: json["email"], - languageId: json["languageId"], - ); + WorkOrderAssignedEmployee({this.userId, this.userName, this.email, this.employeeId, this.languageId, this.extensionNo, this.phoneNumber, this.positionName, this.position, this.isActive}); + + WorkOrderAssignedEmployee.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + positionName = json['positionName']; + position = json['position']; + isActive = json['isActive']; } - Map toJson() => { - "userId": userId, - "userName": userName, - "email": email, - "languageId": languageId, - }; + Map toJson() { + final Map data = new Map(); + data['userId'] = this.userId; + data['userName'] = this.userName; + data['email'] = this.email; + data['employeeId'] = this.employeeId; + data['languageId'] = this.languageId; + data['extensionNo'] = this.extensionNo; + data['phoneNumber'] = this.phoneNumber; + data['positionName'] = this.positionName; + data['position'] = this.position; + data['isActive'] = this.isActive; + return data; + } } class AssetLoan { diff --git a/lib/models/system_notification_model.dart b/lib/models/system_notification_model.dart index f307b289..e2cf4b39 100644 --- a/lib/models/system_notification_model.dart +++ b/lib/models/system_notification_model.dart @@ -13,7 +13,7 @@ class SystemNotificationModel { String? modifiedOn; String? priorityName; String? statusName; - int ? transactionType; + int? transactionType; SystemNotificationModel( {this.userId, @@ -29,7 +29,7 @@ class SystemNotificationModel { this.createdOn, this.modifiedOn, this.priorityName, - this.transactionType, + this.transactionType, this.statusName}); SystemNotificationModel.fromJson(Map? json) { diff --git a/lib/models/user.dart b/lib/models/user.dart index 0bb4ab23..2fd42598 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -107,12 +108,13 @@ class User { return UsersTypes.assessor; case "R-19": // Head Nurse Role return UsersTypes.assessorTl; + case "R-3": // Quality User + return UsersTypes.qualityUser; default: return null; } } - Map toUpdateProfileJson() { Map jsonObject = {}; // if (departmentId != null) jsonObject["department"] = departmentId; diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 992c0060..440715e9 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -72,52 +72,44 @@ class _ServiceRequestDetailViewState extends State { ? const CircularProgressIndicator(color: AppColor.primary10).center : requestProvider.currentWorkOrder == null ? const NoDataFound() - : Stack( - children: [ - Column( + : Column( + children: [ + SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), - initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - if (context.userProvider.user!.type == UsersTypes.engineer && - !requestProvider.isReadOnlyRequest && - requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ - costCard(context, requestProvider), - 12.height, - ], - contactInfoCard(context, requestProvider.currentWorkOrder!.data!), - 20.height, - ], - ), - ).expanded, - SafeArea( - top: false, - right: false, - left: false, - child: FooterActionButton.requestDetailsFooterWidget( - workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, - status: requestProvider.currentWorkOrder?.data?.status, - isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, - activities: requestProvider.currentWorkOrder!.data?.activities ?? [], - userProvider: _userProvider, - context: context), - ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), + workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), + initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), + 12.height, + assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), + 12.height, + if (context.userProvider.user!.type == UsersTypes.engineer && + !requestProvider.isReadOnlyRequest && + requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ + costCard(context, requestProvider), + 12.height, + ], + contactInfoCard(context, requestProvider.currentWorkOrder!.data!), + 20.height, ], ), - //no need to show timer as discussed with backend - // if (requestProvider.timer != null && requestProvider.timer!.isActive) ...[ - // const TimerWidget(), - // ] - ], - ); + ).expanded, + SafeArea( + top: false, + right: false, + left: false, + child: FooterActionButton.requestDetailsFooterWidget( + workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, + status: requestProvider.currentWorkOrder?.data?.status, + isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, + activities: requestProvider.currentWorkOrder!.data?.activities ?? [], + userProvider: _userProvider, + context: context), + ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), + ], + ); }); } diff --git a/lib/modules/cm_module/views/forms/asset_retired/asset_retired.dart b/lib/modules/cm_module/views/forms/asset_retired/asset_retired.dart index 34416d06..e36e430a 100644 --- a/lib/modules/cm_module/views/forms/asset_retired/asset_retired.dart +++ b/lib/modules/cm_module/views/forms/asset_retired/asset_retired.dart @@ -55,8 +55,6 @@ class _AssetRetiredState extends State with TickerProviderStateMix @override Widget build(BuildContext context) { - - return Scaffold( key: _scaffoldKey, appBar: DefaultAppBar(title: context.translation.assetToBeRetired), @@ -125,9 +123,9 @@ class _AssetRetiredState extends State with TickerProviderStateMix onPressed: () async { requestDetailProvider.assetRetiredHelperModel?.activityAssetToBeRetiredAttachments = []; for (var item in _attachments) { - String fileName = ServiceRequestUtils.isLocalUrl(item.name??'') ? ("${item.name??''.split("/").last}|${base64Encode(File(item.name??'').readAsBytesSync())}") :item.name??''; - requestDetailProvider.assetRetiredHelperModel?.activityAssetToBeRetiredAttachments - ?.add(ActivityAssetToBeRetiredAttachments(id: item.id, name: fileName)); + String fileName = + ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + requestDetailProvider.assetRetiredHelperModel?.activityAssetToBeRetiredAttachments?.add(ActivityAssetToBeRetiredAttachments(id: item.id, name: fileName)); } int status = await requestDetailProvider.createActivityAssetToBeRetired(); if (status == 200) { diff --git a/lib/modules/cm_module/views/service_request_detail_main_view.dart b/lib/modules/cm_module/views/service_request_detail_main_view.dart index bd89910c..7f9ad154 100644 --- a/lib/modules/cm_module/views/service_request_detail_main_view.dart +++ b/lib/modules/cm_module/views/service_request_detail_main_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; @@ -10,6 +11,9 @@ import 'package:test_sa/models/enums/work_order_next_step.dart'; import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_page.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/survey/survey_page.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -30,10 +34,13 @@ class ServiceRequestDetailMain extends StatefulWidget { class _ServiceRequestDetailMainState extends State { late ServiceRequestDetailProvider _requestProvider; + static const int moduleId = 1; + @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of(context, listen: false).reset(); getInitialData(); }); } @@ -81,6 +88,37 @@ class _ServiceRequestDetailMainState extends State { Navigator.pop(context); }, actions: [ + if (context.userProvider.isEngineer || context.userProvider.isNurse) + Selector( + selector: (_, myModel) => myModel.isLoading, // Selects only the userName + builder: (_, isLoading, __) { + if (isLoading) { + return const SizedBox(); + } else { + ServiceRequestDetailProvider provider = Provider.of(context, listen: false); + int? statusValue = provider.currentWorkOrder?.data?.status?.value; + if (statusValue == 2 || statusValue == 3 || statusValue == 5 || statusValue == 6) { + getChatToken(moduleId, provider.currentWorkOrder?.data?.workOrderNo ?? ""); + return Consumer(builder: (pContext, requestProvider, _) { + return IconButton( + icon: const Icon(Icons.chat_bubble), + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ChatPage( + moduleId: moduleId, + requestId: widget.requestId, + title: _requestProvider.currentWorkOrder?.data?.workOrderNo ?? "", + readOnly: _requestProvider.isReadOnlyRequest, + ))); + }, + ).toShimmer(context: context, isShow: requestProvider.chatLoginTokenLoading, radius: 30, height: 30, width: 30); + }); + } + return const SizedBox(); + } + }), isNurse ? IconButton( icon: 'qr'.toSvgAsset( @@ -140,4 +178,17 @@ class _ServiceRequestDetailMainState extends State { )), ); } + + void getChatToken(int moduleId, String title) { + ChatProvider cProvider = Provider.of(context, listen: false); + if (cProvider.chatLoginResponse != null && cProvider.referenceID == widget.requestId) return; + String assigneeEmployeeNumber = Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; + String myEmployeeId = context.userProvider.user!.username!; + + String receiver = context.userProvider.isNurse + ? assigneeEmployeeNumber + : (context.userProvider.isEngineer ? Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId! : ""); + + cProvider.getUserAutoLoginTokenSilent(moduleId, widget.requestId, title, myEmployeeId, receiver); + } } diff --git a/lib/modules/cx_module/chat/api_client.dart b/lib/modules/cx_module/chat/api_client.dart new file mode 100644 index 00000000..63e57b93 --- /dev/null +++ b/lib/modules/cx_module/chat/api_client.dart @@ -0,0 +1,295 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:http/io_client.dart'; +// ignore_for_file: avoid_annotating_with_dynamic + +typedef FactoryConstructor = U Function(dynamic); + +class APIError { + dynamic errorCode; + int? errorType; + String? errorMessage; + int? errorStatusCode; + + APIError(this.errorCode, this.errorMessage, this.errorType, this.errorStatusCode); + + Map toJson() => {'errorCode': errorCode, 'errorMessage': errorMessage, 'errorType': errorType, 'ErrorStatusCode': errorStatusCode}; + + @override + String toString() { + return jsonEncode(this); + } +} + +APIException _throwAPIException(Response response) { + switch (response.statusCode) { + case 200: + APIError? apiError; + if (response.body != null && response.body.isNotEmpty) { + var jsonError = jsonDecode(response.body); + print(jsonError); + apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']); + } + return APIException(APIException.BAD_REQUEST, error: apiError); + case 400: + APIError? apiError; + if (response.body != null && response.body.isNotEmpty) { + var jsonError = jsonDecode(response.body); + apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']); + } + return APIException(APIException.BAD_REQUEST, error: apiError); + case 401: + return APIException(APIException.UNAUTHORIZED); + case 403: + return APIException(APIException.FORBIDDEN); + case 404: + return APIException(APIException.NOT_FOUND); + case 500: + return APIException(APIException.INTERNAL_SERVER_ERROR); + case 444: + var downloadUrl = response.headers["location"]; + return APIException(APIException.UPGRADE_REQUIRED, arguments: downloadUrl); + default: + return APIException(APIException.OTHER); + } +} + +class ApiClient { + static final ApiClient _instance = ApiClient._internal(); + + ApiClient._internal(); + + factory ApiClient() => _instance; + + Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, + {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { + var _headers = {'Accept': 'application/json'}; + if (headers != null && headers.isNotEmpty) { + _headers.addAll(headers); + } + if (!kReleaseMode) { + print("Url:$url"); + var bodyJson = json.encode(jsonObject); + print("body:$bodyJson"); + } + var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData); + try { + if (!kReleaseMode) { + // logger.i("Url: " + url); + // logger.i("res: " + response.body); + } + + var jsonData = jsonDecode(response.body); + if (jsonData["IsAuthenticated"] != null) { + // AppState().setIsAuthenticated = jsonData["IsAuthenticated"]; + } + + // if(url.contains("GetOfferDiscountsConfigData")) { + // jsonData["ErrorMessage"] = "Service Not Available"; + // jsonData["ErrorEndUserMessage"] = "Service Not Available"; + // } + + if (jsonData["ErrorMessage"] == null) { + return factoryConstructor(jsonData); + } else if (jsonData["MessageStatus"] == 2 && jsonData["IsOTPMaxLimitExceed"] == true) { + // await Utils.performLogout(AppRoutes.navigatorKey.currentContext, null); + throw const APIException(APIException.UNAUTHORIZED, error: null); + } else { + APIError? apiError; + apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage'], jsonData['ErrorType'] ?? 0, jsonData['ErrorStatusCode']); + throw APIException(APIException.BAD_REQUEST, error: apiError); + } + } catch (ex) { + if (ex is APIException) { + rethrow; + } else { + throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); + } + } + } + + Future postJsonForResponse(String url, T jsonObject, + {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { + String? requestBody; + late Map stringObj; + if (jsonObject != null) { + requestBody = jsonEncode(jsonObject); + if (headers == null) { + headers = {'Content-Type': 'application/json'}; + } else { + headers['Content-Type'] = 'application/json'; + } + } + + if (isFormData) { + headers = {'Content-Type': 'application/x-www-form-urlencoded'}; + stringObj = ((jsonObject ?? {}) as Map).map((key, value) => MapEntry(key, value?.toString() ?? "")); + } + + return await _postForResponse(url, isFormData ? stringObj : requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); + } + + Future _postForResponse(String url, requestBody, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + try { + var _headers = {}; + if (token != null) { + _headers['Authorization'] = 'Bearer $token'; + } + + if (headers != null && headers.isNotEmpty) { + _headers.addAll(headers); + } + + if (queryParameters != null) { + var queryString = new Uri(queryParameters: queryParameters).query; + url = url + '?' + queryString; + } + var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 120)); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return response; + } else { + throw _throwAPIException(response); + } + } on SocketException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } on HttpException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } on TimeoutException catch (e) { + throw APIException(APIException.TIMEOUT, arguments: e); + } on ClientException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } + } + + Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + if (headers == null) { + headers = {'Content-Type': 'application/json'}; + } else { + headers['Content-Type'] = 'application/json'; + } + + if (!kReleaseMode) { + print("Url:$url"); + // var bodyJson = json.encode(jsonObject); + // print("body:$bodyJson"); + } + + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); + } + + Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + try { + var _headers = {}; + if (token != null) { + _headers['Authorization'] = 'Bearer $token'; + } + + if (headers != null && headers.isNotEmpty) { + _headers.addAll(headers); + } + + if (queryParameters != null) { + var queryString = new Uri(queryParameters: queryParameters).query; + url = url + '?' + queryString; + } + var response = await _get(Uri.parse(url), headers: _headers).timeout(Duration(seconds: 60)); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return response; + } else { + throw _throwAPIException(response); + } + } on SocketException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } on HttpException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } on TimeoutException catch (e) { + throw APIException(APIException.TIMEOUT, arguments: e); + } on ClientException catch (e) { + if (retryTimes > 0) { + print('will retry after 3 seconds...'); + await Future.delayed(Duration(seconds: 3)); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } else { + throw APIException(APIException.OTHER, arguments: e); + } + } + } + + Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); + + bool _certificateCheck(X509Certificate cert, String host, int port) => true; + + Future _withClient(Future Function(Client) fn) async { + var httpClient = HttpClient()..badCertificateCallback = _certificateCheck; + var client = IOClient(httpClient); + try { + return await fn(client); + } finally { + client.close(); + } + } + + Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); +} + +class APIException implements Exception { + static const String BAD_REQUEST = 'api_common_bad_request'; + static const String UNAUTHORIZED = 'api_common_unauthorized'; + static const String FORBIDDEN = 'api_common_forbidden'; + static const String NOT_FOUND = 'api_common_not_found'; + static const String INTERNAL_SERVER_ERROR = 'api_common_internal_server_error'; + static const String UPGRADE_REQUIRED = 'api_common_upgrade_required'; + static const String BAD_RESPONSE_FORMAT = 'api_common_bad_response_format'; + static const String OTHER = 'api_common_http_error'; + static const String TIMEOUT = 'api_common_http_timeout'; + static const String UNKNOWN = 'unexpected_error'; + + final String message; + final APIError? error; + final arguments; + + const APIException(this.message, {this.arguments, this.error}); + + Map toJson() => {'message': message, 'error': error, 'arguments': '$arguments'}; + + @override + String toString() { + return jsonEncode(this); + } +} diff --git a/lib/modules/cx_module/chat/chat_api_client.dart b/lib/modules/cx_module/chat/chat_api_client.dart new file mode 100644 index 00000000..b859e305 --- /dev/null +++ b/lib/modules/cx_module/chat/chat_api_client.dart @@ -0,0 +1,480 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/notification/firebase_notification_manger.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:http/http.dart' as http; +import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart'; +import 'api_client.dart'; +import 'model/chat_login_response_model.dart'; +import 'model/chat_participant_model.dart'; +import 'model/get_search_user_chat_model.dart'; +import 'model/get_single_user_chat_list_model.dart'; +import 'model/get_user_login_token_model.dart' as userLoginTokenModel; +import 'model/user_chat_history_model.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/classes/utils.dart'; +// import 'package:mohem_flutter_app/exceptions/api_exception.dart'; +// import 'package:mohem_flutter_app/main.dart'; +// import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart'; +// import 'package:mohem_flutter_app/models/chat/create_group_request.dart' as createGroup; +// import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart'; +// import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; +// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart' as groups; +// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart'; +// import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as user; +// import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; + +class ChatApiClient { + static final ChatApiClient _instance = ChatApiClient._internal(); + + ChatApiClient._internal(); + + factory ChatApiClient() => _instance; + + ChatLoginResponse? chatLoginResponse; + + Future getChatLoginToken(int moduleId, int requestId, String title, String employeeNumber, String assigneeEmployeeNumber) async { + String platform; + if (Platform.isIOS) { + platform = "IOS"; + } else { + if (await FirebaseNotificationManger.isGoogleServicesAvailable()) { + platform = "GOOGLE"; + } else { + platform = "HUAWEI"; + } + } + Response response = await ApiClient().postJsonForResponse(URLs.chatSdkToken, { + "apiKey": URLs.chatApiKey, + "employeeNumber": employeeNumber, + "assigneeEmployeeNumber": assigneeEmployeeNumber, + "userDetails": {"userName": ApiManager.instance.user?.username, "email": ApiManager.instance.user?.email}, + "contextEnabled": true, + "moduleCode": moduleId.toString(), + "referenceId": requestId.toString(), + // "referenceType": "ticket", + "title": title, + "deviceToken": FirebaseNotificationManger.token, + "isHuaweiDevice": platform == "HUAWEI", + "platform": platform, + "voIPToken": null + }); + + if (!kReleaseMode) { + // logger.i("login-res: " + response.body); + } + if (response.statusCode == 200) { + chatLoginResponse = ChatLoginResponse.fromJson(jsonDecode(response.body)); + } + return chatLoginResponse; + } + + Future loadParticipants(int moduleId, int referenceId, String? assigneeEmployeeNumber) async { + Response response = + await ApiClient().getJsonForResponse("${URLs.chatHubUrlApi}/chat/context/$moduleId/$referenceId?assigneeEmployeeNumber=$assigneeEmployeeNumber", token: chatLoginResponse!.token); + + if (!kReleaseMode) { + // logger.i("login-res: " + response.body); + } + if (response.statusCode == 200) { + return ChatParticipantModel.fromJson(jsonDecode(response.body)); + } else { + return null; + } + } + + Future> viewAllDocuments(int moduleId, int referenceId) async { + Response response = await ApiClient().getJsonForResponse("${URLs.chatHubUrlApi}/attachments/conversation?referenceId=$referenceId&moduleCode=$moduleId", token: chatLoginResponse!.token); + + if (response.statusCode == 200) { + List data = jsonDecode(response.body)["response"]; + return data.map((elemet) => ChatAttachment.fromJson(elemet)).toList(); + } else { + return []; + } + } + + Future> loadChatHistory(int moduleId, int referenceId, String myId, String otherId) async { + Response response = await ApiClient().postJsonForResponse( + "${URLs.chatHubUrlApi}/UserChatHistory/GetUserChatHistory/$myId/$otherId/0", {"moduleCode": moduleId.toString(), "referenceId": referenceId.toString()}, + token: chatLoginResponse!.token); + try { + if (response.statusCode == 200) { + List data = jsonDecode(response.body); + return data.map((elemet) => SingleUserChatModel.fromJson(elemet)).toList(); + + // return UserChatHistoryModel.fromJson(jsonDecode(response.body)); + } else { + return []; + } + } catch (ex) { + return []; + } + } + +/* Future sendTextMessage(String message, int conversationId) async { + try { + Response response = + await ApiClient().postJsonForResponse("${URLs.chatHubUrlApi}/chat/conversations/$conversationId/messages", {"content": message, "messageType": "Text"}, token: chatLoginResponse!.token); + + if (response.statusCode == 200) { + return ChatResponse.fromJson(jsonDecode(response.body)); + } else { + return null; + } + } catch (ex) { + print(ex); + return null; + } + }*/ + +// Future getChatMemberFromSearch(String searchParam, int cUserId, int pageNo) async { +// ChatUserModel chatUserModel; +// Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatLoginTokenUrl}getUserWithStatusAndFavAsync", {"employeeNumber": cUserId, "userName": searchParam, "pageNumber": pageNo}, +// token: AppState().chatDetails!.response!.token); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// chatUserModel = chatUserModelFromJson(response.body); +// return chatUserModel; +// } + +// //Get User Recent Chats +// Future getRecentChats() async { +// try { +// Response response = +// +// +// // await ApiManager.instance.get(URLs.getAllRequestsAndCount,h); +// +// +// await ApiClient().getJsonForResponse( +// "${ApiConsts.chatRecentUrl}getchathistorybyuserid", +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return ChatUserModel.fromJson( +// json.decode(response.body), +// ); +// } catch (e) { +// throw e; +// } +// } + +// // Get Favorite Users +// Future getFavUsers() async { +// Response favRes = await ApiClient().getJsonForResponse( +// "${ApiConsts.chatFavUser}getFavUserById/${AppState().chatDetails!.response!.id}", +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + favRes.body); +// } +// return ChatUserModel.fromJson(json.decode(favRes.body)); +// } + +// //Get User Chat History +// Future getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false, required int paginationVal}) async { +// try { +// Response response = await ApiClient().getJsonForResponse( +// "${ApiConsts.chatSingleUserHistoryUrl}GetUserChatHistory/$senderUID/$receiverUID/$paginationVal", +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return response; +// } catch (e) { +// getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); +// throw e; +// } +// } + +// //Favorite Users +// Future favUser({required int userID, required int targetUserID}) async { +// Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatFavUser}addFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); +// return favoriteChatUser; +// } + +// //UnFavorite Users +// Future unFavUser({required int userID, required int targetUserID}) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// "${ApiConsts.chatFavUser}deleteFavUser", +// {"targetUserId": targetUserID, "userId": userID}, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); +// return favoriteChatUser; +// } catch (e) { +// e as APIException; +// throw e; +// } +// } + +// Upload Chat Media + Future uploadMedia(String userId, File file, String fileSource, {Map? jsonData}) async { + dynamic request = MultipartRequest('POST', Uri.parse('${URLs.chatHubUrlApi}/attachments/upload')); + request.fields.addAll({'userId': userId, 'fileSource': fileSource}); + if (jsonData != null) { + request.fields.addAll(jsonData); + } + + request.files.add(await MultipartFile.fromPath('file', file.path)); + request.headers.addAll({'Authorization': 'Bearer ${chatLoginResponse!.token}'}); + StreamedResponse response = await request.send(); + String data = await response.stream.bytesToString(); + if (!kReleaseMode) { + print("uploadMedia: $data"); + } + return jsonDecode(data); + } + +// // Download File For Chat +// Future downloadURL({required String fileName, required String fileTypeDescription, required int fileSource,required String url}) async { +// Response response = await ApiClient().postJsonForResponse( +// "${ApiConsts.chatMediaImageUploadUrl}download", +// {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": fileSource}, +// token: AppState().chatDetails!.response!.token, +// ); +// Uint8List data = Uint8List.fromList(response.bodyBytes); +// return data; +// } + +// Download File For Chat + Future downloadURL(String url, {required String fileName, required String fileTypeDescription, required int fileSource}) async { + Response response = await ApiClient().postJsonForResponse( + url, + {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": fileSource}, + token: chatLoginResponse!.token, + ); + Uint8List data = Uint8List.fromList(response.bodyBytes); + return data; + } + + /// Downloads a file from a secure URL that uses a 302 redirect. + /// + /// [url]: The initial URL that requires the authorization token. + /// [token]: The Bearer token for authorization. + /// [saveFileName]: The name to give the downloaded file (e.g., 'my-document.pdf'). + Future downloadFileWithHttp(String url, {required String fileName, required String fileTypeDescription, required int fileSource}) async { + final client = http.Client(); + File? file; + try { + final request = http.Request('GET', Uri.parse(url)); + request.headers['Authorization'] = 'Bearer ${chatLoginResponse!.token}'; + + // This is the most important part: prevent automatic redirection. + request.followRedirects = false; + + print("Making initial request to: $url"); + final streamedResponse = await client.send(request); + + // --- Step 2: Check the response and handle the redirect --- + + // Check for a 302 redirect status. + if (streamedResponse.statusCode == 302) { + // Get the new URL from the 'Location' header. + final redirectUrl = streamedResponse.headers['location']; + + if (redirectUrl == null) { + throw Exception('302 Redirect did not contain a Location header.'); + } + + final fileResponse = await http.get(Uri.parse(redirectUrl), headers: {'Authorization': 'Bearer ${chatLoginResponse!.token}'}); + + if (fileResponse.statusCode == 200) { + // Get a safe directory to save the file. + final dir = await getTemporaryDirectory(); + file = File('${dir.path}/$fileName'); + + // Write the file to disk. + await file.writeAsBytes(fileResponse.bodyBytes); + print("File downloaded successfully and saved to: ${file.path}"); + } else { + // The download from the final URL failed. + throw Exception('Failed to download from redirect URL. Status: ${fileResponse.statusCode}'); + } + } + // Handle cases where the server might just send the file directly. + else if (streamedResponse.statusCode == 200) { + print("Server sent file directly without redirect."); + final dir = await getTemporaryDirectory(); + file = File('${dir.path}/$fileName'); + await file.writeAsBytes(await streamedResponse.stream.toBytes()); + print("File downloaded successfully and saved to: ${file.path}"); + } + // Handle other error statuses. + else { + throw Exception('Failed to initiate download. Status: ${streamedResponse.statusCode}'); + } + } catch (e) { + print("An error occurred during download: $e"); + } finally { + // Always close the client to free up resources. + client.close(); + } + + return file; + } + +// //Get Chat Users & Favorite Images +// Future> getUsersImages({required List encryptedEmails}) async { +// List imagesData = []; +// Response response = await ApiClient().postJsonForResponse( +// "${ApiConsts.chatUserImages}images", +// {"encryptedEmails": encryptedEmails, "fromClient": false}, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// if (response.statusCode == 200) { +// imagesData = chatUserImageModelFromJson(response.body); +// } else if (response.statusCode == 500 || response.statusCode == 504) { +// getUsersImages(encryptedEmails: encryptedEmails); +// } else { +// Utils.showToast("Something went wrong while loading images"); +// imagesData = []; +// } +// return imagesData; +// } +// +// //group chat apis start here. +// Future getGroupsByUserId() async { +// try { +// Response response = await ApiClient().getJsonForResponse( +// "${ApiConsts.getGroupByUserId}${AppState().chatDetails!.response!.id}", +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return groups.GetUserGroups.fromRawJson(response.body); +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } + +// Future deleteGroup(int? groupId) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// ApiConsts.deleteGroup, +// {"groupId": groupId}, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return response; +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } + +// Future updateGroupAdmin(int? groupId, List groupList) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// ApiConsts.updateGroupAdmin, +// {"groupId": groupId, "groupUserList": groupList}, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return response; +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } + +// Future> getGroupChatHistory(int? groupId, List groupList) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// ApiConsts.getGroupChatHistoryAsync, +// {"groupId": groupId, "targetUserList": groupList, "CurrentId": AppState().chatDetails!.response!.id}, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// List groupChat = []; +// List groupChatData = json.decode(response.body); +// for (var i in groupChatData) { +// groupChat.add(GetGroupChatHistoryAsync.fromJson(i)); +// } +// +// groupChat.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); +// return groupChat; +// // for(GetGroupChatHistoryAsync i in groupChat) { +// // return GetGroupChatHistoryAsync.fromJson(jsonEncode(i)); +// // } +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } + +// Future addGroupAndUsers(createGroup.CreateGroupRequest request) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// ApiConsts.addGroupsAndUsers, +// request, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return response.body; +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } + +// Future updateGroupAndUsers(createGroup.CreateGroupRequest request) async { +// try { +// Response response = await ApiClient().postJsonForResponse( +// ApiConsts.updateGroupsAndUsers, +// request, +// token: AppState().chatDetails!.response!.token, +// ); +// if (!kReleaseMode) { +// logger.i("res: " + response.body); +// } +// return response.body; +// } catch (e) { +// //if fail api returning 500 hence just printing here +// print(e); +// throw e; +// } +// } +} diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart new file mode 100644 index 00000000..0ff1f567 --- /dev/null +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -0,0 +1,786 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:audio_waveforms/audio_waveforms.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/helper/utils.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/chat/view_all_attachment_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +import 'helper/chat_audio_player.dart'; +import 'helper/chat_file_picker.dart'; +import 'helper/chat_file_viewer.dart'; +import 'model/get_single_user_chat_list_model.dart'; +import 'model/user_chat_history_model.dart'; + +enum ChatState { idle, voiceRecordingStarted, voiceRecordingCompleted } + +class ChatPage extends StatefulWidget { + int moduleId; + int requestId; + String? assigneeEmployeeNumber; + String? myLoginUserID; + String? contactEmployeeINumber; + String title; + bool readOnly; + + ChatPage({Key? key, required this.moduleId, required this.requestId, this.title = "Chat", this.readOnly = false, this.assigneeEmployeeNumber, this.myLoginUserID, this.contactEmployeeINumber}) + : super(key: key); + + @override + _ChatPageState createState() { + return _ChatPageState(); + } +} + +class _ChatPageState extends State { + bool isSender = false; + bool isAudioRecording = false; + + String? recordedFilePath; + final RecorderController recorderController = RecorderController(); + PlayerController playerController = PlayerController(); + + TextEditingController textEditingController = TextEditingController(); + + ChatState chatState = ChatState.idle; + + late String receiver; + + @override + void initState() { + super.initState(); + loadChatHistory(); + playerController.addListener(() async { + // if (playerController.playerState == PlayerState.playing && playerController.maxDuration == await playerController.getDuration()) { + // await playerController.stopPlayer(); + // setState(() {}); + // } + }); + } + + void loadChatHistory() { + // // String assigneeEmployeeNumber = Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; + // // String myEmployeeId = context.userProvider.user!.username!; + // // + // + // receiver = context.userProvider.isNurse ? widget.assigneeEmployeeNumber : widget.myEmployeeID; + // + // Provider.of(context, listen: false).connectToHub(widget.moduleId, widget.requestId, widget.myEmployeeID, widget.assigneeEmployeeNumber); + // + // receiver = context.userProvider.isNurse + // ? assigneeEmployeeNumber + // :myEmployeeId; + + String assigneeEmployeeNumber = widget.assigneeEmployeeNumber ?? Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; + String myEmployeeId = widget.myLoginUserID ?? context.userProvider.user!.username!; + + receiver = context.userProvider.isNurse + ? assigneeEmployeeNumber + : (context.userProvider.isEngineer + ? (widget.contactEmployeeINumber ?? Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId!) + : ""); + Provider.of(context, listen: false).connectToHub(widget.moduleId, widget.requestId, myEmployeeId, receiver, widget.readOnly, isMounted: mounted); + } + + @override + void dispose() { + chatHubConnection?.stop(); + playerController.dispose(); + recorderController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.white10, + appBar: DefaultAppBar(title: widget.title), + body: Consumer(builder: (context, chatProvider, child) { + if (chatProvider.chatLoginTokenLoading) return const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3).center; + + if (chatProvider.chatLoginResponse == null) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Failed to connect chat", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w500), + ), + 24.height, + AppFilledButton( + label: "Go Back", + maxWidth: true, + buttonColor: AppColor.primary10, + onPressed: () => Navigator.pop(context), + ).paddingOnly(start: 48, end: 48) + ], + ).center; + } + return Column( + children: [ + Container( + color: AppColor.neutral50, + constraints: const BoxConstraints(maxHeight: 56), + padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), + alignment: Alignment.center, + child: Row( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + chatProvider.recipient?.userName ?? "", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10), + ), + AnimatedSize( + duration: const Duration(milliseconds: 250), + child: SizedBox( + height: chatProvider.isTyping ? null : 0, + child: Text( + "Typing...", + maxLines: 1, + style: AppTextStyles.overline.copyWith(color: AppColor.white10), + ), + )), + // if (chatProvider.isTyping) + // Text( + // "Typing...", + // maxLines: 1, + // style: AppTextStyles.tinyFont2.copyWith(color: AppColor.white10), + // ), + ], + ).expanded, + 4.width, + Text( + "View All Documents", + style: AppTextStyles.bodyText.copyWith( + color: AppColor.white10, + decoration: TextDecoration.underline, + decorationColor: AppColor.white10, + ), + ).onPress(() { + Navigator.push(context, CupertinoPageRoute(builder: (context) => ViewAllAttachmentPage(moduleId: widget.moduleId, requestId: widget.requestId))); + }), + ], + ), + ), + Container( + color: AppColor.neutral100, + child: chatProvider.userChatHistoryLoading + ? ListView( + padding: const EdgeInsets.all(16), + children: [ + recipientMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true), + recipientMsgCard(false, null, msg: "testing", loading: true), + recipientMsgCard(false, null, msg: "testing testing testing", loading: true), + senderMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true), + senderMsgCard(false, null, msg: "Please let me know what is the issue?", loading: true), + ], + ) + : chatProvider.chatResponseList.isEmpty + ? Text( + "Send a message to start conversation", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50.withOpacity(.5), fontWeight: FontWeight.w500), + ).center + : ListView.builder( + padding: const EdgeInsets.all(16), + reverse: true, + itemBuilder: (cxt, index) { + final currentMessage = chatProvider.chatResponseList[index]; + final bool showSenderName = + (index == chatProvider.chatResponseList.length - 1) || (currentMessage.currentUserId != chatProvider.chatResponseList[index + 1].currentUserId); + bool isSender = chatProvider.chatResponseList[index].currentUserId == chatProvider.sender?.userId!; + bool showDateHeader = false; + if (index == chatProvider.chatResponseList.length - 1) { + showDateHeader = true; + } else { + final nextMessage = chatProvider.chatResponseList[index + 1]; + final currentDate = DateUtils.dateOnly(currentMessage.createdDate!); + final nextDate = DateUtils.dateOnly(nextMessage.createdDate!); + if (!currentDate.isAtSameMomentAs(nextDate)) { + showDateHeader = true; + } + } + return Column(mainAxisSize: MainAxisSize.min, children: [ + if (showDateHeader) dateCard(currentMessage.createdDate?.toString().chatMsgDateWithYear ?? ""), + isSender ? senderMsgCard(showSenderName, chatProvider.chatResponseList[index]) : recipientMsgCard(showSenderName, chatProvider.chatResponseList[index]) + ]); + }, + itemCount: chatProvider.chatResponseList.length)) + .expanded, + if (!widget.readOnly) ...[ + Divider(height: 1, thickness: 1, color: const Color(0xff767676).withOpacity(.11)), + SafeArea( + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 56), + child: Row( + children: [ + if (chatState == ChatState.idle) ...[ + TextFormField( + controller: textEditingController, + cursorColor: AppColor.neutral50, + style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50), + minLines: 1, + maxLines: 3, + textInputAction: TextInputAction.none, + keyboardType: TextInputType.multiline, + onTap: () { + chatHubConnection!.invoke("SendTypingAsync", args: [receiver]); + }, + onTapOutside: (PointerDownEvent event) { + chatHubConnection!.invoke("SendStopTypingAsync", args: [receiver]); + }, + onChanged: (text) { + chatHubConnection!.invoke("SendTypingAsync", args: [receiver]); + }, + decoration: InputDecoration( + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + border: InputBorder.none, + errorBorder: InputBorder.none, + contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8), + alignLabelWithHint: true, + filled: true, + constraints: const BoxConstraints(), + suffixIconConstraints: const BoxConstraints(), + hintText: "Type your message here...", + hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)), + ), + ).expanded, + IconButton( + onPressed: () async { + FocusScope.of(context).unfocus(); + File? file = (await showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + clipBehavior: Clip.antiAliasWithSaveLayer, + builder: (BuildContext context) => ChatFilePicker())) as File?; + if (file != null) { + Utils.showUploadingDialog(context); + await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1"); + Utils.hideLoading(context); + } + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: "chat_attachment".toSvgAsset(width: 24, height: 24), + constraints: const BoxConstraints(), + ), + IconButton( + onPressed: () async { + await recorderController.checkPermission(); + if (recorderController.hasPermission) { + chatState = ChatState.voiceRecordingStarted; + recorderController.record(); + setState(() {}); + } else { + "Audio permission denied. Please enable from setting".showToast; + } + // if (!isPermissionGranted) { + // "Audio permission denied. Please enable from setting".showToast; + // return; + // } + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: "chat_mic".toSvgAsset(width: 24, height: 24), + constraints: const BoxConstraints(), + ), + ] else if (chatState == ChatState.voiceRecordingStarted) ...[ + AudioWaveforms( + size: Size(MediaQuery.of(context).size.width, 56.0), + waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false), + padding: const EdgeInsets.only(left: 16), + recorderController: recorderController, // Customize how waveforms looks. + ).expanded, + IconButton( + onPressed: () async { + isAudioRecording = false; + await recorderController.pause(); + recordedFilePath = await recorderController.stop(); + chatState = ChatState.voiceRecordingCompleted; + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: Icon(Icons.stop_circle_rounded), + constraints: const BoxConstraints(), + ) + ] else if (chatState == ChatState.voiceRecordingCompleted) ...[ + if (playerController.playerState == PlayerState.playing) + IconButton( + onPressed: () async { + await playerController.pausePlayer(); + await playerController.stopPlayer(); + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: const Icon(Icons.stop_circle_outlined, size: 20), + constraints: const BoxConstraints(), + ) + else + IconButton( + onPressed: () async { + await playerController.preparePlayer(path: recordedFilePath!); + await playerController.startPlayer(); + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: const Icon(Icons.play_circle_fill_rounded, size: 20), + constraints: const BoxConstraints(), + ), + AudioFileWaveforms( + playerController: playerController, + waveformData: recorderController.waveData, + enableSeekGesture: false, + continuousWaveform: false, + waveformType: WaveformType.long, + playerWaveStyle: const PlayerWaveStyle( + fixedWaveColor: AppColor.neutral50, + liveWaveColor: AppColor.primary10, + showSeekLine: true, + ), + size: Size(MediaQuery.of(context).size.width, 56.0), + ).expanded, + IconButton( + onPressed: () async { + await playerController.stopPlayer(); + recorderController.reset(); + recordedFilePath = null; + chatState = ChatState.idle; + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: "delete_icon".toSvgAsset(width: 24, height: 24), + constraints: const BoxConstraints(), + ), + ], + + // if (recordedFilePath == null) ...[ + // isAudioRecording + // ? AudioWaveforms( + // size: Size(MediaQuery.of(context).size.width, 56.0), + // + // // enableGesture: true, + // + // waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false), + // padding: const EdgeInsets.only(left: 16), + // recorderController: recorderController, // Customize how waveforms looks. + // ).expanded + // : TextFormField( + // cursorColor: AppColor.neutral50, + // style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50), + // minLines: 1, + // maxLines: 3, + // textInputAction: TextInputAction.none, + // keyboardType: TextInputType.multiline, + // decoration: InputDecoration( + // enabledBorder: InputBorder.none, + // focusedBorder: InputBorder.none, + // border: InputBorder.none, + // errorBorder: InputBorder.none, + // contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8), + // alignLabelWithHint: true, + // filled: true, + // constraints: const BoxConstraints(), + // suffixIconConstraints: const BoxConstraints(), + // hintText: "Type your message here...", + // hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)), + // // suffixIcon: Row( + // // mainAxisSize: MainAxisSize.min, + // // crossAxisAlignment: CrossAxisAlignment.end, + // // mainAxisAlignment: MainAxisAlignment.end, + // // children: [ + // // + // // 8.width, + // // ], + // // ) + // ), + // ).expanded, + // IconButton( + // onPressed: () {}, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: "chat_attachment".toSvgAsset(width: 24, height: 24), + // constraints: const BoxConstraints(), + // ), + // ], + // if (recordedFilePath == null) + // ...[] + // else ...[ + // IconButton( + // onPressed: () async { + // await playerController.preparePlayer(path: recordedFilePath!); + // playerController.startPlayer(); + // }, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: const Icon(Icons.play_circle_fill_rounded, size: 20), + // constraints: const BoxConstraints(), + // ), + // AudioFileWaveforms( + // playerController: playerController, + // size: Size(300, 50), + // ).expanded, + // IconButton( + // onPressed: () async { + // playerController.pausePlayer(); + // }, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: const Icon(Icons.pause_circle_filled_outlined, size: 20), + // constraints: const BoxConstraints(), + // ), + // IconButton( + // onPressed: () {}, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: "delete".toSvgAsset(width: 24, height: 24), + // constraints: const BoxConstraints(), + // ), + // ], + // if (isAudioRecording && recorderController.isRecording) + // IconButton( + // onPressed: () {}, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: "chat_msg_send".toSvgAsset(width: 24, height: 24), + // constraints: const BoxConstraints(), + // ), + // if (isAudioRecording) + // IconButton( + // onPressed: () async { + // isAudioRecording = false; + // await recorderController.pause(); + // recordedFilePath = await recorderController.stop(); + // chatState = ChatState.voiceRecordingCompleted; + // setState(() {}); + // }, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: Icon(Icons.stop_circle_rounded), + // constraints: const BoxConstraints(), + // ) + // else + // IconButton( + // onPressed: () async { + // await recorderController.checkPermission(); + // if (recorderController.hasPermission) { + // setState(() { + // isAudioRecording = true; + // }); + // recorderController.record(); + // } else { + // "Audio permission denied. Please enable from setting".showToast; + // } + // // if (!isPermissionGranted) { + // // "Audio permission denied. Please enable from setting".showToast; + // // return; + // // } + // }, + // style: const ButtonStyle( + // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + // ), + // icon: "chat_mic".toSvgAsset(width: 24, height: 24), + // constraints: const BoxConstraints(), + // ), + + IconButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + onPressed: () async { + if (chatState == ChatState.voiceRecordingCompleted) { + Utils.showUploadingDialog(context); + try { + await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1"); + Utils.hideLoading(context); + + await playerController.stopPlayer(); + recorderController.reset(); + recordedFilePath = null; + chatState = ChatState.idle; + setState(() {}); + } catch (ex) { + Utils.hideLoading(context); + } + } else { + if (textEditingController.text.isEmpty) return; + chatProvider.invokeSendMessage({ + "Contant": textEditingController.text, + // "ContantNo": "0cc8b126-6180-4f91-a64d-2f62443b3f3f", + // "CreatedDate": "2025-11-09T18:58:12.502Z", + "CurrentEmployeeNumber": context.userProvider.user!.username!, + "ChatEventId": 1, + "ConversationId": chatProvider.chatParticipantModel!.id!.toString(), + "ModuleCode": widget.moduleId.toString(), + "ReferenceNumber": widget.requestId.toString(), + "UserChatHistoryLineRequestList": [ + {"TargetEmployeeNumber": receiver, "TargetUserStatus": 1, "IsSeen": false, "IsDelivered": true, "SeenOn": null, "DeliveredOn": null} + ] + }).then((success) { + if (success) { + textEditingController.clear(); + } + }); + } + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: chatProvider.messageIsSending + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ) + : "chat_msg_send".toSvgAsset(width: 24, height: 24), + constraints: const BoxConstraints(), + ), + 8.width, + ], + ), + ), + ) + ] + ], + ); + })); + } + + Widget dateCard(String date) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + margin: const EdgeInsets.only(top: 16, bottom: 8), + decoration: BoxDecoration( + color: AppColor.neutral50, + borderRadius: BorderRadius.circular(6), + ), + child: Text(date, style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10))) + .center; + } + + Widget senderMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) { + Widget senderHeader = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${chatResponse?.currentUserName ?? "User"}(Me)", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600), + ).toShimmer(context: context, isShow: loading), + 8.width, + Container( + height: 26, + width: 26, + decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey), + ).toShimmer(context: context, isShow: loading), + ], + ); + + return Align( + alignment: Alignment.centerRight, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (showHeader) ...[senderHeader, 4.height] else 8.height, + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flexible( + fit: FlexFit.loose, + child: Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(right: 8, left: 26 + 8), + decoration: BoxDecoration( + color: loading ? Colors.transparent : AppColor.white10, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (chatResponse?.downloadUrl != null) ...[ + // if (chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "audio") + // // ASoundPlayer(audio: chatResponse.downloadUrl!) + // + // ChatAudioPlayer(chatResponse!.fileTypeResponse!, chatResponse.downloadUrl!) + // // if ( + // // + // // chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "image" + // // + // // ) + // + // else + ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), + + // Container( + // width: 250, + // height: 250, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(12), + // border: Border.all(color: Colors.grey.withOpacity(.5), width: 1), + // image: DecorationImage( + // image: NetworkImage(chatResponse.downloadUrl!, headers: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}), + // ), + // ), + // // child: CachedNetworkImage( + // // imageUrl: chatResponse.downloadUrl! ?? "", + // // // fit: boxFit ?? BoxFit.cover, + // // // alignment: Alignment.center, + // // // width: width, + // // // height: height, + // // httpHeaders: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}, + // // placeholder: (context, url) => const Center(child: CircularProgressIndicator()), + // // // errorWidget: (context, url, error) => Icon(showDefaultIcon ? Icons.image_outlined : Icons.broken_image_rounded), + // // ), + // + // // Image.network( + // // chatResponse.downloadUrl!, + // // headers: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}, + // // ), + // ) + ] else + Text( + chatResponse?.contant ?? msg, + style: AppTextStyles.bodyText2.copyWith(color: AppColor.neutral120), + ).toShimmer(context: context, isShow: loading), + if (loading) 4.height, + Text( + chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM", + style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.neutral50.withOpacity(0.5)), + ).toShimmer(context: context, isShow: loading), + ], + )), + ), + if (chatResponse != null) + if (chatResponse.isSeen!) + "chat_seen".toSvgAsset(width: 16, height: 16) + else if (chatResponse.isDelivered!) + "chat_delivered".toSvgAsset(width: 16, height: 16) + else + "chat_sent".toSvgAsset(width: 16, height: 16), + (26 + 8).width, + ], + ), + ], + ), + ); + } + + Widget recipientMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) { + String extraSpaces = ""; + int length = 0; + if ((chatResponse?.contant ?? "").isNotEmpty) { + if (chatResponse!.contant!.length < 8) { + length = 8 - chatResponse.contant!.length; + } + } + + String contentMsg = chatResponse?.contant == null ? msg : chatResponse!.contant! + extraSpaces; + Widget recipientHeader = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 26, + width: 26, + decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey), + ).toShimmer(context: context, isShow: loading), + 8.width, + Text( + chatResponse?.currentUserName ?? "User", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600), + ).toShimmer(context: context, isShow: loading) + ], + ); + + return Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showHeader) ...[recipientHeader, 4.height] else 8.height, + Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(left: 26 + 8, right: 26 + 8), + decoration: BoxDecoration( + color: loading ? Colors.transparent : AppColor.primary10, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (chatResponse?.downloadUrl != null) ...[ + ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), + ] else + Text( + contentMsg, + style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10), + ).paddingOnly(end: 6 * length).toShimmer(context: context, isShow: loading), + if (loading) 4.height, + Align( + alignment: Alignment.centerRight, + widthFactor: 1, + child: Text( + chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM", + style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.white10), + ), + ).toShimmer(context: context, isShow: loading), + ], + )), + ], + ), + ); + } +} diff --git a/lib/modules/cx_module/chat/chat_provider.dart b/lib/modules/cx_module/chat/chat_provider.dart new file mode 100644 index 00000000..136dd28b --- /dev/null +++ b/lib/modules/cx_module/chat/chat_provider.dart @@ -0,0 +1,2161 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:audio_waveforms/audio_waveforms.dart'; + +// import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart'; +import 'package:intl/intl.dart'; +import 'package:just_audio/just_audio.dart' as JustAudio; +import 'package:just_audio/just_audio.dart'; + +// import 'package:mohem_flutter_app/api/chat/chat_api_client.dart'; +// import 'package:mohem_flutter_app/api/my_team/my_team_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/classes/encryption.dart'; +// import 'package:mohem_flutter_app/classes/utils.dart'; +// import 'package:mohem_flutter_app/config/routes.dart'; +// import 'package:mohem_flutter_app/main.dart'; +// import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart'; +// import 'package:mohem_flutter_app/models/chat/create_group_request.dart' as createGroup; +// import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart' as groupchathistory; +// import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; +// import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; +// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart' as groups; +// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart'; +// import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as userLoginToken; +// import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; +// import 'package:mohem_flutter_app/models/chat/target_users.dart'; +// import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart'; +// import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart'; +// import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; +// import 'package:mohem_flutter_app/widgets/image_picker.dart'; +// import 'package:open_filex/open_filex.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:signalr_netcore/hub_connection.dart'; +import 'package:signalr_netcore/signalr_client.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/modules/cx_module/chat/model/chat_login_response_model.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter/material.dart' as Material; + +import 'chat_api_client.dart'; +import 'model/chat_attachment_model.dart'; +import 'model/chat_participant_model.dart'; +import 'model/get_search_user_chat_model.dart'; +import 'model/get_single_user_chat_list_model.dart'; +import 'model/user_chat_history_model.dart'; +// import 'get_single_user_chat_list_model.dart'; + +HubConnection? chatHubConnection; + +class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { + bool isTyping = false; + + bool chatLoginTokenLoading = false; + ChatLoginResponse? chatLoginResponse; + + bool chatParticipantLoading = false; + ChatParticipantModel? chatParticipantModel; + + bool userChatHistoryLoading = false; + + // UserChatHistoryModel? userChatHistory; + + List? userChatHistory; + + bool messageIsSending = false; + + List chatResponseList = []; + + Participants? sender; + Participants? recipient; + + late String receiverID; + late int moduleID; + int? referenceID; + + void reset() { + chatHubConnection?.stop().then((value) { + chatHubConnection = null; + }); + chatLoginTokenLoading = false; + chatParticipantLoading = false; + userChatHistoryLoading = false; + chatLoginResponse = null; + chatParticipantModel = null; + userChatHistory = null; + sender = null; + recipient = null; + ChatApiClient().chatLoginResponse = null; + } + + // Future getUserAutoLoginToken(int moduleId, int requestId, String title, String myId, String assigneeEmployeeNumber) async { + // reset(); + // chatLoginTokenLoading = true; + // notifyListeners(); + // chatLoginResponse = await ChatApiClient().getChatLoginToken(moduleId, requestId, title, myId); + // chatLoginTokenLoading = false; + // chatParticipantLoading = true; + // notifyListeners(); + // // loadParticipants(moduleId, requestId); + // if (chatLoginResponse != null) { + // await loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); + // } + // } + + Future getUserAutoLoginTokenSilent(int moduleId, int requestId, String title, String myId, String assigneeEmployeeNumber, {bool isMounted = true}) async { + reset(); + receiverID = assigneeEmployeeNumber; + chatLoginTokenLoading = true; + if (isMounted) { + notifyListeners(); + } + try { + chatLoginResponse = await ChatApiClient().getChatLoginToken(moduleId, requestId, title, myId, assigneeEmployeeNumber); + chatParticipantModel = await ChatApiClient().loadParticipants(moduleId, requestId, assigneeEmployeeNumber); + sender = chatParticipantModel?.participants?.firstWhere((participant) => participant.employeeNumber == myId); + recipient = chatParticipantModel?.participants?.firstWhere((participant) => participant.employeeNumber == assigneeEmployeeNumber); + } catch (ex) { + print(ex); + } + chatLoginTokenLoading = false; + if (isMounted) { + notifyListeners(); + } + } + + // Future getUserLoadChatHistory(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { + // await loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); + // } + + // Future loadParticipants(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { + // userChatHistoryLoading = true; + // notifyListeners(); + // try { + // chatParticipantModel = await ChatApiClient().loadParticipants(moduleId, requestId, assigneeEmployeeNumber); + // } catch (ex) { + // userChatHistoryLoading = false; + // notifyListeners(); + // return; + // } + // + // try { + // sender = chatParticipantModel?.participants?.singleWhere((participant) => participant.employeeNumber == myId); + // recipient = chatParticipantModel?.participants?.singleWhere((participant) => participant.employeeNumber == assigneeEmployeeNumber); + // } catch (e) {} + // } + + Future connectToHub(int moduleId, int requestId, String myId, String assigneeEmployeeNumber, bool readOnly, {bool isMounted = true}) async { + userChatHistoryLoading = true; + if (isMounted) { + notifyListeners(); + } + moduleID = moduleId; + referenceID = requestId; + if (!readOnly) { + await buildHubConnection(chatParticipantModel!.id!.toString()); + } + + userChatHistory = await ChatApiClient().loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); + chatResponseList = userChatHistory ?? []; + chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); + userChatHistoryLoading = false; + if (isMounted) { + notifyListeners(); + } + } + + // Future loadChatHistory(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { + // userChatHistoryLoading = true; + // notifyListeners(); + // userChatHistory = await ChatApiClient().loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); + // chatResponseList = userChatHistory ?? []; + // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); + // userChatHistoryLoading = false; + // notifyListeners(); + // } + + Future invokeSendMessage(Object object) async { + messageIsSending = true; + notifyListeners(); + bool returnStatus = false; + try { + await chatHubConnection!.invoke("AddChatUserAsync", args: [object]); + returnStatus = true; + } catch (ex) {} + + messageIsSending = false; + notifyListeners(); + return returnStatus; + } + + // Future sendTextMessage(String message) async { + // messageIsSending = true; + // notifyListeners(); + // bool returnStatus = false; + // + // ChatResponse? chatResponse = await ChatApiClient().sendTextMessage(message, chatParticipantModel!.id!); + // if (chatResponse != null) { + // returnStatus = true; + // // chatResponseList.add(chatResponse); + // try { + // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); + // } catch (ex) {} + // } + // messageIsSending = false; + // notifyListeners(); + // return returnStatus; + // } + + // List? uGroups = [], searchGroups = []; + + // Future getUserAutoLoginToken() async { + // userLoginToken.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + // + // if (userLoginResponse.StatusCode == 500) { + // disbaleChatForThisUser = true; + // notifyListeners(); + // } + // + // if (userLoginResponse.response != null) { + // // AppState().setchatUserDetails = userLoginResponse; + // } else { + // // AppState().setchatUserDetails = userLoginResponse; + // userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr".showToast; + // disbaleChatForThisUser = true; + // notifyListeners(); + // } + // } + + Future buildHubConnection(String conversationID) async { + chatHubConnection = await getHubConnection(); + await chatHubConnection!.start(); + if (kDebugMode) { + print("Hub Conn: Startedddddddd"); + } + + await chatHubConnection!.invoke("JoinConversation", args: [conversationID]); + chatHubConnection!.on("ReceiveMessage", onMsgReceived1); + chatHubConnection!.on("OnMessageReceivedAsync", onMsgReceived); + chatHubConnection!.on("OnSubmitChatAsync", OnSubmitChatAsync); + chatHubConnection!.on("OnTypingAsync", OnTypingAsync); + chatHubConnection!.on("OnStopTypingAsync", OnStopTypingAsync); + + //group On message + + // chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); + } + + Future getHubConnection() async { + HubConnection hub; + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hub = HubConnectionBuilder() + .withUrl("${URLs.chatHubUrlChat}?UserId=${chatLoginResponse!.userId}&source=Desktop&access_token=${chatLoginResponse!.token}", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); + return hub; + } + + void registerEvents() { + // chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); + // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); + + // chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync); + // chatHubConnection.on("OnUserTypingAsync", onUserTyping); + chatHubConnection?.on("OnUserCountAsync", userCountAsync); + // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); + // chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); + // chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); + // chatHubConnection.on("OnGetGroupUserStatusAsync", getGroupUserStatus); + + // + // {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]} + + if (kDebugMode) { + // logger.i("All listeners registered"); + } + } + + // Future getUserRecentChats() async { + // ChatUserModel recentChat = await ChatApiClient().getRecentChats(); + // ChatUserModel favUList = await ChatApiClient().getFavUsers(); + // // userGroups = await ChatApiClient().getGroupsByUserId(); + // if (favUList.response != null && recentChat.response != null) { + // favUsersList = favUList.response!; + // favUsersList.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); + // for (dynamic user in recentChat.response!) { + // for (dynamic favUser in favUList.response!) { + // if (user.id == favUser.id) { + // user.isFav = favUser.isFav; + // } + // } + // } + // } + // pChatHistory = recentChat.response ?? []; + // uGroups = userGroups.groupresponse ?? []; + // pChatHistory!.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); + // searchedChats = pChatHistory; + // isLoading = false; + // await invokeUserChatHistoryNotDeliveredAsync(userId: int.parse(AppState().chatDetails!.response!.id.toString())); + // sort(); + // notifyListeners(); + // if (searchedChats!.isNotEmpty || favUsersList.isNotEmpty) { + // getUserImages(); + // } + // } + + Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { + await chatHubConnection!.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); + return ""; + } + + // void getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false}) async { + // isLoading = true; + // if (isNewChat) userChatHistory = []; + // if (!loadMore) paginationVal = 0; + // isChatScreenActive = true; + // receiverID = receiverUID; + // Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); + // if (response.statusCode == 204) { + // if (isNewChat) { + // userChatHistory = []; + // } else if (loadMore) {} + // } else { + // if (loadMore) { + // List temp = getSingleUserChatModel(response.body).reversed.toList(); + // userChatHistory.addAll(temp); + // } else { + // userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); + // } + // } + // isLoading = false; + // notifyListeners(); + // + // if (isChatScreenActive && receiverUID == receiverID) { + // markRead(userChatHistory, receiverUID); + // } + // + // generateConvId(); + // } + // + // void generateConvId() async { + // Uuid uuid = const Uuid(); + // chatCID = uuid.v4(); + // } + + void markRead(List data, String receiverID) { + for (SingleUserChatModel element in data) { + // if (AppState().chatDetails!.response!.id! == element.targetUserId) { + if (element.isSeen != null) { + if (!element.isSeen!) { + element.isSeen = true; + dynamic data = [ + { + "userChatHistoryId": element.userChatHistoryId, + "TargetUserId": element.currentUserId == receiverID ? element.currentUserId : element.targetUserId, + "isDelivered": true, + "isSeen": true, + } + ]; + updateUserChatHistoryStatusAsync(data); + notifyListeners(); + } + // } + } + } + // notifyListeners(); + } + + void updateUserChatHistoryStatusAsync(List data) { + try { + chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } + } + + void updateUserChatHistoryOnMsg(List data) { + try { + chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } + } + + // List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); + List getSingleUserChatModel(String str) { + final dynamic decodedJson = json.decode(str); + + // Check if the decoded JSON is already a List + if (decodedJson is List) { + return List.from(decodedJson.map((x) => SingleUserChatModel.fromJson(x))); + } + // If it's a Map (a single object), wrap it in a list + else if (decodedJson is Map) { + return [SingleUserChatModel.fromJson(decodedJson)]; + } + // Handle unexpected types + else { + throw const FormatException('Expected a JSON object or a list of JSON objects.'); + } + } + + // List getGroupChatHistoryAsync(String str) => + // List.from(json.decode(str).map((x) => groupchathistory.GetGroupChatHistoryAsync.fromJson(x))); + // + Future uploadAttachments(String userId, File file, String fileSource) async { + dynamic result; + try { + Map jsonData = { + "IsContextual": true.toString(), + "ModuleCode": moduleID.toString(), + "ReferenceId": referenceID.toString(), + "ReferenceType": "ticket", + "ConversationId": chatParticipantModel!.id.toString(), + "TargetUserId": receiverID, + "SendMessage": true.toString(), + }; + + Object? response = await ChatApiClient().uploadMedia(userId, file, fileSource, jsonData: jsonData); + if (response != null) { + result = response; + } else { + result = []; + } + } catch (e) { + throw e; + } + return result; + } + + // void updateUserChatStatus(List? args) { + // dynamic items = args!.toList(); + // for (var cItem in items[0]) { + // for (SingleUserChatModel chat in userChatHistory) { + // if (cItem["contantNo"].toString() == chat.contantNo.toString()) { + // chat.isSeen = cItem["isSeen"]; + // chat.isDelivered = cItem["isDelivered"]; + // } + // } + // } + // notifyListeners(); + // } + + void getGroupUserStatus(List? args) { + //note: need to implement this function... + print(args); + } + + void onChatSeen(List? args) { + dynamic items = args!.toList(); + // for (var user in searchedChats!) { + // if (user.id == items.first["id"]) { + // user.userStatus = items.first["userStatus"]; + // } + // } + // notifyListeners(); + } + + void userCountAsync(List? args) { + dynamic items = args!.toList(); + // logger.d(items); + //logger.d("---------------------------------User Count Async -------------------------------------"); + //logger.d(items); + // for (var user in searchedChats!) { + // if (user.id == items.first["id"]) { + // user.userStatus = items.first["userStatus"]; + // } + // } + // notifyListeners(); + } + + // void updateChatHistoryWindow(List? args) { + // dynamic items = args!.toList(); + // if (kDebugMode) { + // logger.i("---------------------------------Update Chat History Windows Async -------------------------------------"); + // } + // logger.d(items); + // // for (var user in searchedChats!) { + // // if (user.id == items.first["id"]) { + // // user.userStatus = items.first["userStatus"]; + // // } + // // } + // // notifyListeners(); + // } + + // void chatNotDelivered(List? args) { + // dynamic items = args!.toList(); + // for (dynamic item in items[0]) { + // for (ChatUser element in searchedChats!) { + // if (element.id == item["currentUserId"]) { + // int? val = element.unreadMessageCount ?? 0; + // element.unreadMessageCount = val! + 1; + // } + // } + // } + // notifyListeners(); + // } + // + // void changeStatus(List? args) { + // dynamic items = args!.toList(); + // for (ChatUser user in searchedChats!) { + // if (user.id == items.first["id"]) { + // user.userStatus = items.first["userStatus"]; + // } + // } + // if (teamMembersList.isNotEmpty) { + // for (ChatUser user in teamMembersList!) { + // if (user.id == items.first["id"]) { + // user.userStatus = items.first["userStatus"]; + // } + // } + // } + // + // notifyListeners(); + // } + // + // void filter(String value) async { + // List? tmp = []; + // if (value.isEmpty || value == "") { + // tmp = pChatHistory; + // } else { + // for (ChatUser element in pChatHistory!) { + // if (element.userName!.toLowerCase().contains(value.toLowerCase())) { + // tmp.add(element); + // } + // } + // } + // searchedChats = tmp; + // notifyListeners(); + // } + + Timer? timer; + + Future OnTypingAsync(List? parameters) async { + // String empId = parameters!.first as String; + isTyping = true; + notifyListeners(); + if (timer?.isActive ?? false) { + timer!.cancel(); + } + timer = Timer(const Duration(milliseconds: 2500), () { + isTyping = false; + notifyListeners(); + }); + } + + Future OnStopTypingAsync(List? parameters) async { + if (timer?.isActive ?? false) { + timer!.cancel(); + } + isTyping = false; + notifyListeners(); + } + + // Future OnSubmitChatAsync(List? parameters) async { + // // List data = jsonDecode(parameters!.first!.toString()); + // + // print("OnSubmitChatAsync:$parameters"); + // + // for (dynamic msg in parameters!) { + // var data = getSingleUserChatModel(jsonEncode(msg)); + // print(data); + // } + // + // // List data = parameters!.first!; + // // chatResponseList = chatResponseList + data.map((elemet) => ChatHistoryResponse.fromJson(element)).toList(); + // // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); + // // notifyListeners(); + // } + + Future onMsgReceived1(List? parameters) async { + print("onMsgReceived1:$parameters"); + } + + Future onMsgReceived(List? parameters) async { + List data = [], temp = []; + print("OnMessageReceivedAsync:$parameters"); + for (dynamic msg in parameters!) { + data = getSingleUserChatModel(jsonEncode(msg)); + // temp = getSingleUserChatModel(jsonEncode(msg)); + // data.first.targetUserId = temp.first.currentUserId; + // data.first.targetUserName = temp.first.currentUserName; + // data.first.targetUserEmail = temp.first.currentUserEmail; + // data.first.currentUserId = temp.first.targetUserId; + // data.first.currentUserName = temp.first.targetUserName; + // data.first.currentUserEmail = temp.first.targetUserEmail; + + // if (data.first.fileTypeId == 12 || data.first.fileTypeId == 4 || data.first.fileTypeId == 3) { + // data.first.image = await ChatApiClient().downloadURL(fileName: data.first.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 1); + // } + // if (data.first.userChatReplyResponse != null) { + // if (data.first.fileTypeResponse != null) { + // if (data.first.userChatReplyResponse!.fileTypeId == 12 || data.first.userChatReplyResponse!.fileTypeId == 4 || data.first.userChatReplyResponse!.fileTypeId == 3) { + // data.first.userChatReplyResponse!.image = await ChatApiClient() + // .downloadURL(fileName: data.first.userChatReplyResponse!.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 1); + // data.first.userChatReplyResponse!.isImageLoaded = true; + // } + // } + // } + } + // + // if + // + // ( + // + // searchedChats != null) { + // dynamic contain = searchedChats!.where((ChatUser element) => element.id == data.first.currentUserId); + // if (contain.isEmpty) { + // List emails = []; + // emails.add(await EmailImageEncryption().encrypt(val: data.first.currentUserEmail!)); + // List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); + // searchedChats!.add( + // ChatUser( + // id: data.first.currentUserId, + // userName: data.first.currentUserName, + // email: data.first.currentUserEmail, + // unreadMessageCount: 0, + // isImageLoading: false, + // image: chatImages!.first.profilePicture ?? "", + // isImageLoaded: true, + // userStatus: 1, + // isTyping: false, + // userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, data.first.currentUserId.toString()), + // ), + // ); + // } + // } + // setMsgTune(); + // userChatHistory = userChatHistory! + data; + // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); + userChatHistory?.insert(0, data.first); + notifyListeners(); + + // markRead(data, data.first.targetUserId!); + // if (isChatScreenActive && data.first.currentUserId == receiverID) { + // + // } else { + // // if (searchedChats != null) { + // // for (ChatUser user in searchedChats!) { + // // if (user.id == data.first.currentUserId) { + // // int tempCount = user.unreadMessageCount ?? 0; + // // user.unreadMessageCount = tempCount + 1; + // // } + // // } + // // sort(); + // // } + // } + + // List list = [ + // { + // "userChatHistoryId": data.first.userChatHistoryId, + // "TargetUserId": temp.first.targetUserId, + // "isDelivered": true, + // "isSeen": isChatScreenActive && data.first.currentUserId == receiverID ? true : false + // } + // ]; + // updateUserChatHistoryOnMsg(list); + // invokeChatCounter(userId: AppState().chatDetails!.response!. + // id + // + // ! + // + // ); + + // notifyListeners + // + // ( + // + // ); + } + +// Future onGroupMsgReceived(List? parameters) async { +// List data = [], temp = []; +// +// for (dynamic msg in parameters!) { +// // groupChatHistory.add(groupchathistory.GetGroupChatHistoryAsync.fromJson(msg)); +// data.add(groupchathistory.GetGroupChatHistoryAsync.fromJson(msg)); +// temp = data; +// // data.first.currentUserId = temp.first.currentUserId; +// // data.first.currentUserName = temp.first.currentUserName; +// // +// // data.first.currentUserId = temp.first.currentUserId; +// // data.first.currentUserName = temp.first.currentUserName; +// +// if (data.first.fileTypeId == 12 || data.first.fileTypeId == 4 || data.first.fileTypeId == 3) { +// data.first.image = await ChatApiClient().downloadURL(fileName: data.first.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 2); +// } +// if (data.first.groupChatReplyResponse != null) { +// if (data.first.fileTypeResponse != null) { +// if (data.first.groupChatReplyResponse!.fileTypeId == 12 || data.first.groupChatReplyResponse!.fileTypeId == 4 || data.first.groupChatReplyResponse!.fileTypeId == 3) { +// data.first.groupChatReplyResponse!.image = await ChatApiClient() +// .downloadURL(fileName: data.first.groupChatReplyResponse!.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg", fileSource: 2); +// data.first.groupChatReplyResponse!.isImageLoaded = true; +// } +// } +// } +// } +// +// // if (searchedChats != null) { +// // dynamic contain = searchedChats! +// // .where((ChatUser element) => element.id == data.first.currentUserId); +// // if (contain.isEmpty) { +// // List emails = []; +// // emails.add(await EmailImageEncryption() +// // .encrypt(val: data.first.currentUserEmail!)); +// // List chatImages = +// // await ChatApiClient().getUsersImages(encryptedEmails: emails); +// // searchedChats!.add( +// // ChatUser( +// // id: data.first.currentUserId, +// // userName: data.first.currentUserName, +// // email: data.first.currentUserEmail, +// // unreadMessageCount: 0, +// // isImageLoading: false, +// // image: chatImages!.first.profilePicture ?? "", +// // isImageLoaded: true, +// // userStatus: 1, +// // isTyping: false, +// // userLocalDownlaodedImage: await downloadImageLocal( +// // chatImages.first.profilePicture, +// // data.first.currentUserId.toString()), +// // ), +// // ); +// // } +// // } +// groupChatHistory.insert(0, data.first); +// setMsgTune(); +// // if (isChatScreenActive && data.first.currentUserId == receiverID) { +// +// // } else { +// // if (searchedChats != null) { +// // for (ChatUser user in searchedChats!) { +// // if (user.id == data.first.currentUserId) { +// // int tempCount = user.unreadMessageCount ?? 0; +// // user.unreadMessageCount = tempCount + 1; +// // } +// // } +// sort(); +// //} +// //} +// // +// // List list = [ +// // { +// // "userChatHistoryId": data.first.groupId, +// // "TargetUserId": temp.first.currentUserId, +// // "isDelivered": true, +// // "isSeen": isChatScreenActive && data.first.currentUserId == receiverID +// // ? true +// // : false +// // } +// // ]; +// // updateUserChatHistoryOnMsg(list); +// // invokeChatCounter(userId: AppState().chatDetails!.response!.id!); +// notifyListeners(); +// } + + void OnSubmitChatAsync(List? parameters) { + print("OnSubmitChatAsync:$parameters"); + List data = []; + for (dynamic msg in parameters!) { + data = getSingleUserChatModel(jsonEncode(msg)); + } + chatResponseList.insert(0, data.first); + notifyListeners(); + } + +// void sort() { +// searchedChats!.sort( +// (ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo(a.unreadMessageCount!), +// ); +// } + +// void onUserTyping(List? parameters) { +// for (ChatUser user in searchedChats!) { +// if (user.id == parameters![1] && parameters[0] == true) { +// user.isTyping = parameters[0] as bool?; +// Future.delayed( +// const Duration(seconds: 2), +// () { +// user.isTyping = false; +// notifyListeners(); +// }, +// ); +// } +// } +// notifyListeners(); +} + +int getFileType(String value) { + switch (value) { + case ".pdf": + return 1; + case ".png": + return 3; + case ".txt": + return 5; + case ".jpg": + return 12; + case ".jpeg": + return 4; + case ".xls": + return 7; + case ".xlsx": + return 7; + case ".doc": + return 6; + case ".docx": + return 6; + case ".ppt": + return 8; + case ".pptx": + return 8; + case ".zip": + return 2; + case ".rar": + return 2; + case ".aac": + return 13; + case ".mp3": + return 14; + case ".mp4": + return 16; + case ".mov": + return 16; + case ".avi": + return 16; + case ".flv": + return 16; + + default: + return 0; + } +} + +String getFileTypeDescription(String value) { + switch (value) { + case ".pdf": + return "application/pdf"; + case ".png": + return "image/png"; + case ".txt": + return "text/plain"; + case ".jpg": + return "image/jpg"; + case ".jpeg": + return "image/jpeg"; + case ".ppt": + return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case ".pptx": + return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case ".doc": + return "application/vnd.openxmlformats-officedocument.wordprocessingm"; + case ".docx": + return "application/vnd.openxmlformats-officedocument.wordprocessingm"; + case ".xls": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case ".xlsx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case ".zip": + return "application/octet-stream"; + case ".rar": + return "application/octet-stream"; + case ".aac": + return "audio/aac"; + case ".mp3": + return "audio/mp3"; + case ".mp4": + return "video/mp4"; + case ".avi": + return "video/avi"; + case ".flv": + return "video/flv"; + case ".mov": + return "video/mov"; + + default: + return ""; + } +} + +// Future sendChatToServer( +// {required int chatEventId, +// required fileTypeId, +// required int targetUserId, +// required String targetUserName, +// required chatReplyId, +// required bool isAttachment, +// required bool isReply, +// Uint8List? image, +// required bool isImageLoaded, +// String? userEmail, +// int? userStatus, +// File? voiceFile, +// required bool isVoiceAttached}) async { +// Uuid uuid = const Uuid(); +// String contentNo = uuid.v4(); +// String msg; +// if (isVoiceAttached) { +// msg = voiceFile!.path.split("/").last; +// } else { +// msg = message.text; +// logger.w(msg); +// } +// SingleUserChatModel data = SingleUserChatModel( +// userChatHistoryId: 0, +// chatEventId: chatEventId, +// chatSource: 1, +// contant: msg, +// contantNo: contentNo, +// conversationId: chatCID, +// createdDate: DateTime.now(), +// currentUserId: AppState().chatDetails!.response!.id, +// currentUserName: AppState().chatDetails!.response!.userName, +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// isReplied: false, +// fileTypeId: fileTypeId, +// userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null, +// fileTypeResponse: isAttachment +// ? FileTypeResponse( +// fileTypeId: fileTypeId, +// fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(), +// fileKind: "file", +// fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last, +// fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString()), +// ) +// : null, +// image: image, +// isImageLoaded: isImageLoaded, +// voice: isVoiceMsg ? voiceFile! : null, +// voiceController: isVoiceMsg ? AudioPlayer() : null); +// if (kDebugMode) { +// logger.i("model data: " + jsonEncode(data)); +// } +// userChatHistory.insert(0, data); +// isTextMsg = false; +// isReplyMsg = false; +// isAttachmentMsg = false; +// isVoiceMsg = false; +// sFileType = ""; +// message.clear(); +// notifyListeners(); +// +// String chatData = +// '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; +// +// await chatHubConnection.invoke("AddChatUserAsync", args: [json.decode(chatData)]); +// } + +//groupChatMessage + +// Future sendGroupChatToServer( +// {required int chatEventId, +// required fileTypeId, +// required int targetGroupId, +// required String targetUserName, +// required chatReplyId, +// required bool isAttachment, +// required bool isReply, +// Uint8List? image, +// required bool isImageLoaded, +// String? userEmail, +// int? userStatus, +// File? voiceFile, +// required bool isVoiceAttached, +// required List userList}) async { +// Uuid uuid = const Uuid(); +// String contentNo = uuid.v4(); +// String msg; +// if (isVoiceAttached) { +// msg = voiceFile!.path.split("/").last; +// } else { +// msg = message.text; +// logger.w(msg); +// } +// groupchathistory.GetGroupChatHistoryAsync data = groupchathistory.GetGroupChatHistoryAsync( +// //userChatHistoryId: 0, +// chatEventId: chatEventId, +// chatSource: 1, +// contant: msg, +// contantNo: contentNo, +// conversationId: chatCID, +// createdDate: DateTime.now().toString(), +// currentUserId: AppState().chatDetails!.response!.id, +// currentUserName: AppState().chatDetails!.response!.userName, +// groupId: targetGroupId, +// groupName: targetUserName, +// isReplied: false, +// fileTypeId: fileTypeId, +// fileTypeResponse: isAttachment +// ? groupchathistory.FileTypeResponse( +// fileTypeId: fileTypeId, +// fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(), +// fileKind: "file", +// fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last, +// fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString())) +// : null, +// image: image, +// isImageLoaded: isImageLoaded, +// voice: isVoiceMsg ? voiceFile! : null, +// voiceController: isVoiceMsg ? AudioPlayer() : null); +// if (kDebugMode) { +// logger.i("model data: " + jsonEncode(data)); +// } +// groupChatHistory.insert(0, data); +// isTextMsg = false; +// isReplyMsg = false; +// isAttachmentMsg = false; +// isVoiceMsg = false; +// sFileType = ""; +// message.clear(); +// notifyListeners(); +// +// List targetUsers = []; +// +// for (GroupUserList element in userList) { +// targetUsers.add(TargetUsers(isDelivered: false, isSeen: false, targetUserId: element.id, userAction: element.userAction, userStatus: element.userStatus)); +// } +// +// String chatData = +// '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}'; +// +// await chatHubConnection.invoke("AddGroupChatHistoryAsync", args: [json.decode(chatData)]); +// } + +// void sendGroupChatMessage( +// BuildContext context, { +// required int targetUserId, +// required int userStatus, +// required String userEmail, +// required String targetUserName, +// required List userList, +// }) async { +// if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { +// logger.d("// Normal Text Message"); +// if (message.text.isEmpty) { +// return; +// } +// sendGroupChatToServer( +// chatEventId: 1, +// fileTypeId: null, +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: false, +// chatReplyId: null, +// isReply: false, +// isImageLoaded: false, +// image: null, +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// } else if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && isReplyMsg) { +// logger.d("// Text Message as Reply"); +// if (message.text.isEmpty) { +// return; +// } +// sendGroupChatToServer( +// chatEventId: 1, +// fileTypeId: null, +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: groupChatReplyData.first.groupChatHistoryId, +// isAttachment: false, +// isReply: true, +// isImageLoaded: groupChatReplyData.first.isImageLoaded!, +// image: groupChatReplyData.first.image, +// isVoiceAttached: false, +// voiceFile: null, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// } +// // Attachment +// else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { +// logger.d("// Normal Image Message"); +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, "2"); +// String? ext = getFileExtension(selectedFile.path); +// Utils.hideLoading(context); +// sendGroupChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: true, +// chatReplyId: null, +// isReply: false, +// isImageLoaded: true, +// image: selectedFile.readAsBytesSync(), +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// } else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && isReplyMsg) { +// logger.d("// Image as Reply Msg"); +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, "2"); +// String? ext = getFileExtension(selectedFile.path); +// Utils.hideLoading(context); +// sendGroupChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: true, +// chatReplyId: repliedMsg.first.userChatHistoryId, +// isReply: true, +// isImageLoaded: true, +// image: selectedFile.readAsBytesSync(), +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// } +// //Voice +// +// else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && !isReplyMsg) { +// logger.d("// Normal Voice Message"); +// +// if (!isPause) { +// path = await recorderController.stop(false); +// } +// if (kDebugMode) { +// logger.i("path:" + path!); +// } +// File voiceFile = File(path!); +// voiceFile.readAsBytesSync(); +// _timer?.cancel(); +// isPause = false; +// isPlaying = false; +// isRecoding = false; +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, "2"); +// String? ext = getFileExtension(voiceFile.path); +// Utils.hideLoading(context); +// sendGroupChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// //, +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: null, +// isAttachment: true, +// isReply: isReplyMsg, +// isImageLoaded: false, +// voiceFile: voiceFile, +// isVoiceAttached: true, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// notifyListeners(); +// } else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && isReplyMsg) { +// logger.d("// Voice as Reply Msg"); +// +// if (!isPause) { +// path = await recorderController.stop(false); +// } +// if (kDebugMode) { +// logger.i("path:" + path!); +// } +// File voiceFile = File(path!); +// voiceFile.readAsBytesSync(); +// _timer?.cancel(); +// isPause = false; +// isPlaying = false; +// isRecoding = false; +// +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, "2"); +// String? ext = getFileExtension(voiceFile.path); +// Utils.hideLoading(context); +// sendGroupChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetGroupId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: null, +// isAttachment: true, +// isReply: isReplyMsg, +// isImageLoaded: false, +// voiceFile: voiceFile, +// isVoiceAttached: true, +// userEmail: userEmail, +// userStatus: userStatus, +// userList: userList); +// notifyListeners(); +// } +// if (searchedChats != null) { +// dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); +// if (contain.isEmpty) { +// List emails = []; +// emails.add(await EmailImageEncryption().encrypt(val: userEmail)); +// List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); +// searchedChats!.add( +// ChatUser( +// id: targetUserId, +// userName: targetUserName, +// unreadMessageCount: 0, +// email: userEmail, +// isImageLoading: false, +// image: chatImages.first.profilePicture ?? "", +// isImageLoaded: true, +// isTyping: false, +// isFav: false, +// userStatus: userStatus, +// // userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()), +// ), +// ); +// notifyListeners(); +// } +// } +// } +// +// void sendChatMessage( +// BuildContext context, { +// required int targetUserId, +// required int userStatus, +// required String userEmail, +// required String targetUserName, +// }) async { +// if (kDebugMode) { +// print("====================== Values ============================"); +// print("Is Text " + isTextMsg.toString()); +// print("isReply " + isReplyMsg.toString()); +// print("isAttachment " + isAttachmentMsg.toString()); +// print("isVoice " + isVoiceMsg.toString()); +// } +// //Text +// if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { +// // logger.d("// Normal Text Message"); +// if (message.text.isEmpty) { +// return; +// } +// sendChatToServer( +// chatEventId: 1, +// fileTypeId: null, +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: false, +// chatReplyId: null, +// isReply: false, +// isImageLoaded: false, +// image: null, +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus); +// } else if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && isReplyMsg) { +// logger.d("// Text Message as Reply"); +// if (message.text.isEmpty) { +// return; +// } +// sendChatToServer( +// chatEventId: 1, +// fileTypeId: null, +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: repliedMsg.first.userChatHistoryId, +// isAttachment: false, +// isReply: true, +// isImageLoaded: repliedMsg.first.isImageLoaded!, +// image: repliedMsg.first.image, +// isVoiceAttached: false, +// voiceFile: null, +// userEmail: userEmail, +// userStatus: userStatus); +// } +// // Attachment +// else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && !isReplyMsg) { +// logger.d("// Normal Image Message"); +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, "1"); +// String? ext = getFileExtension(selectedFile.path); +// Utils.hideLoading(context); +// sendChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: true, +// chatReplyId: null, +// isReply: false, +// isImageLoaded: true, +// image: selectedFile.readAsBytesSync(), +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus); +// } else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && isReplyMsg) { +// logger.d("// Image as Reply Msg"); +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile, "1"); +// String? ext = getFileExtension(selectedFile.path); +// Utils.hideLoading(context); +// sendChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// isAttachment: true, +// chatReplyId: repliedMsg.first.userChatHistoryId, +// isReply: true, +// isImageLoaded: true, +// image: selectedFile.readAsBytesSync(), +// isVoiceAttached: false, +// userEmail: userEmail, +// userStatus: userStatus); +// } +// //Voice +// +// else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && !isReplyMsg) { +// logger.d("// Normal Voice Message"); +// +// if (!isPause) { +// path = await recorderController.stop(false); +// } +// if (kDebugMode) { +// logger.i("path:" + path!); +// } +// File voiceFile = File(path!); +// voiceFile.readAsBytesSync(); +// _timer?.cancel(); +// isPause = false; +// isPlaying = false; +// isRecoding = false; +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, "1"); +// String? ext = getFileExtension(voiceFile.path); +// Utils.hideLoading(context); +// sendChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: null, +// isAttachment: true, +// isReply: isReplyMsg, +// isImageLoaded: false, +// voiceFile: voiceFile, +// isVoiceAttached: true, +// userEmail: userEmail, +// userStatus: userStatus); +// notifyListeners(); +// } else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && isReplyMsg) { +// logger.d("// Voice as Reply Msg"); +// +// if (!isPause) { +// path = await recorderController.stop(false); +// } +// if (kDebugMode) { +// logger.i("path:" + path!); +// } +// File voiceFile = File(path!); +// voiceFile.readAsBytesSync(); +// _timer?.cancel(); +// isPause = false; +// isPlaying = false; +// isRecoding = false; +// +// Utils.showLoading(context); +// dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile, "1"); +// String? ext = getFileExtension(voiceFile.path); +// Utils.hideLoading(context); +// sendChatToServer( +// chatEventId: 2, +// fileTypeId: getFileType(ext.toString()), +// targetUserId: targetUserId, +// targetUserName: targetUserName, +// chatReplyId: null, +// isAttachment: true, +// isReply: isReplyMsg, +// isImageLoaded: false, +// voiceFile: voiceFile, +// isVoiceAttached: true, +// userEmail: userEmail, +// userStatus: userStatus); +// notifyListeners(); +// } +// if (searchedChats != null) { +// dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); +// if (contain.isEmpty) { +// List emails = []; +// emails.add(await EmailImageEncryption().encrypt(val: userEmail)); +// List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); +// searchedChats!.add( +// ChatUser( +// id: targetUserId, +// userName: targetUserName, +// unreadMessageCount: 0, +// email: userEmail, +// isImageLoading: false, +// image: chatImages.first.profilePicture ?? "", +// isImageLoaded: true, +// isTyping: false, +// isFav: false, +// userStatus: userStatus, +// userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()), +// ), +// ); +// notifyListeners(); +// } +// } +// // else { +// // List emails = []; +// // emails.add(await EmailImageEncryption().encrypt(val: userEmail)); +// // List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); +// // searchedChats!.add( +// // ChatUser( +// // id: targetUserId, +// // userName: targetUserName, +// // unreadMessageCount: 0, +// // email: userEmail, +// // isImageLoading: false, +// // image: chatImages.first.profilePicture ?? "", +// // isImageLoaded: true, +// // isTyping: false, +// // isFav: false, +// // userStatus: userStatus, +// // userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()), +// // ), +// // ); +// // notifyListeners(); +// // } +// } + +// void selectImageToUpload(BuildContext context) { +// ImageOptions.showImageOptionsNew(context, true, (String image, File file) async { +// if (checkFileSize(file.path)) { +// selectedFile = file; +// isAttachmentMsg = true; +// isTextMsg = false; +// sFileType = getFileExtension(file.path)!; +// message.text = file.path.split("/").last; +// Navigator.of(context).pop(); +// } else { +// Utils.showToast("Max 1 mb size is allowed to upload"); +// } +// notifyListeners(); +// }); +// } + +// void removeAttachment() { +// isAttachmentMsg = false; +// sFileType = ""; +// message.text = ''; +// notifyListeners(); +// } + +String? getFileExtension(String fileName) { + try { + if (kDebugMode) { + // logger.i("ext: " + "." + fileName.split('.').last); + } + return "." + fileName.split('.').last; + } catch (e) { + return null; + } +} + +bool checkFileSize(String path) { + int fileSizeLimit = 5120; + File f = File(path); + double fileSizeInKB = f.lengthSync() / 5000; + double fileSizeInMB = fileSizeInKB / 5000; + if (fileSizeInKB > fileSizeLimit) { + return false; + } else { + return true; + } +} + +String getType(String type) { + switch (type) { + case ".pdf": + return "assets/images/pdf.svg"; + case ".png": + return "assets/images/png.svg"; + case ".txt": + return "assets/icons/chat/txt.svg"; + case ".jpg": + return "assets/images/jpg.svg"; + case ".jpeg": + return "assets/images/jpg.svg"; + case ".xls": + return "assets/icons/chat/xls.svg"; + case ".xlsx": + return "assets/icons/chat/xls.svg"; + case ".doc": + return "assets/icons/chat/doc.svg"; + case ".docx": + return "assets/icons/chat/doc.svg"; + case ".ppt": + return "assets/icons/chat/ppt.svg"; + case ".pptx": + return "assets/icons/chat/ppt.svg"; + case ".zip": + return "assets/icons/chat/zip.svg"; + case ".rar": + return "assets/icons/chat/zip.svg"; + case ".aac": + return "assets/icons/chat/aac.svg"; + case ".mp3": + return "assets/icons/chat/zip.mp3"; + default: + return "assets/images/thumb.svg"; + } +} + +// void chatReply(SingleUserChatModel data) { +// repliedMsg = []; +// data.isReplied = true; +// isReplyMsg = true; +// repliedMsg.add(data); +// notifyListeners(); +// } + +// void groupChatReply(groupchathistory.GetGroupChatHistoryAsync data) { +// groupChatReplyData = []; +// data.isReplied = true; +// isReplyMsg = true; +// groupChatReplyData.add(data); +// notifyListeners(); +// } + +// void closeMe() { +// repliedMsg = []; +// isReplyMsg = false; +// notifyListeners(); +// } + +String dateFormte(DateTime data) { + DateFormat f = DateFormat('hh:mm a dd MMM yyyy', "en_US"); + f.format(data); + return f.format(data); +} + +// Future favoriteUser({required int userID, required int targetUserID, required bool fromSearch}) async { +// fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().favUser(userID: userID, targetUserID: targetUserID); +// if (favoriteChatUser.response != null) { +// for (ChatUser user in searchedChats!) { +// if (user.id == favoriteChatUser.response!.targetUserId!) { +// user.isFav = favoriteChatUser.response!.isFav; +// dynamic contain = favUsersList!.where((ChatUser element) => element.id == favoriteChatUser.response!.targetUserId!); +// if (contain.isEmpty) { +// favUsersList.add(user); +// } +// } +// } +// +// for (ChatUser user in chatUsersList!) { +// if (user.id == favoriteChatUser.response!.targetUserId!) { +// user.isFav = favoriteChatUser.response!.isFav; +// dynamic contain = favUsersList!.where((ChatUser element) => element.id == favoriteChatUser.response!.targetUserId!); +// if (contain.isEmpty) { +// favUsersList.add(user); +// } +// } +// } +// } +// if (fromSearch) { +// for (ChatUser user in favUsersList) { +// if (user.id == targetUserID) { +// user.userLocalDownlaodedImage = null; +// user.isImageLoading = false; +// user.isImageLoaded = false; +// } +// } +// } +// notifyListeners(); +// } +// +// Future unFavoriteUser({required int userID, required int targetUserID}) async { +// fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().unFavUser(userID: userID, targetUserID: targetUserID); +// +// if (favoriteChatUser.response != null) { +// for (ChatUser user in searchedChats!) { +// if (user.id == favoriteChatUser.response!.targetUserId!) { +// user.isFav = favoriteChatUser.response!.isFav; +// } +// } +// favUsersList.removeWhere( +// (ChatUser element) => element.id == targetUserID, +// ); +// } +// +// for (ChatUser user in chatUsersList!) { +// if (user.id == favoriteChatUser.response!.targetUserId!) { +// user.isFav = favoriteChatUser.response!.isFav; +// } +// } +// +// notifyListeners(); +// } + +// void clearSelections() { +// searchedChats = pChatHistory; +// search.clear(); +// isChatScreenActive = false; +// receiverID = 0; +// paginationVal = 0; +// message.text = ''; +// isAttachmentMsg = false; +// repliedMsg = []; +// sFileType = ""; +// isReplyMsg = false; +// isTextMsg = false; +// isVoiceMsg = false; +// notifyListeners(); +// } +// +// void clearAll() { +// searchedChats = pChatHistory; +// search.clear(); +// isChatScreenActive = false; +// receiverID = 0; +// paginationVal = 0; +// message.text = ''; +// isTextMsg = false; +// isAttachmentMsg = false; +// isVoiceMsg = false; +// isReplyMsg = false; +// repliedMsg = []; +// sFileType = ""; +// } +// +// void disposeData() { +// if (!disbaleChatForThisUser) { +// search.clear(); +// isChatScreenActive = false; +// receiverID = 0; +// paginationVal = 0; +// message.text = ''; +// isTextMsg = false; +// isAttachmentMsg = false; +// isVoiceMsg = false; +// isReplyMsg = false; +// repliedMsg = []; +// sFileType = ""; +// deleteData(); +// favUsersList.clear(); +// searchedChats?.clear(); +// pChatHistory?.clear(); +// // uGroups?.clear(); +// searchGroup?.clear(); +// chatHubConnection.stop(); +// // AppState().chatDetails = null; +// } +// } +// +// void deleteData() { +// List exists = [], unique = []; +// if (searchedChats != null) exists.addAll(searchedChats!); +// exists.addAll(favUsersList!); +// Map profileMap = {}; +// for (ChatUser item in exists) { +// profileMap[item.email!] = item; +// } +// unique = profileMap.values.toList(); +// for (ChatUser element in unique!) { +// deleteFile(element.id.toString()); +// } +// } + +// void getUserImages() async { +// List emails = []; +// List exists = [], unique = []; +// exists.addAll(searchedChats!); +// exists.addAll(favUsersList!); +// Map profileMap = {}; +// for (ChatUser item in exists) { +// profileMap[item.email!] = item; +// } +// unique = profileMap.values.toList(); +// for (ChatUser element in unique!) { +// emails.add(await EmailImageEncryption().encrypt(val: element.email!)); +// } +// +// List chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails); +// for (ChatUser user in searchedChats!) { +// for (ChatUserImageModel uImage in chatImages) { +// if (user.email == uImage.email) { +// user.image = uImage.profilePicture ?? ""; +// user.userLocalDownlaodedImage = await downloadImageLocal(uImage.profilePicture, user.id.toString()); +// user.isImageLoading = false; +// user.isImageLoaded = true; +// } +// } +// } +// for (ChatUser favUser in favUsersList) { +// for (ChatUserImageModel uImage in chatImages) { +// if (favUser.email == uImage.email) { +// favUser.image = uImage.profilePicture ?? ""; +// favUser.userLocalDownlaodedImage = await downloadImageLocal(uImage.profilePicture, favUser.id.toString()); +// favUser.isImageLoading = false; +// favUser.isImageLoaded = true; +// } +// } +// } +// +// notifyListeners(); +// } + +Future downloadImageLocal(String? encodedBytes, String userID) async { + File? myfile; + if (encodedBytes == null) { + return myfile; + } else { + await deleteFile(userID); + Uint8List decodedBytes = base64Decode(encodedBytes); + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); + String dirPath = '${appDocumentsDirectory.path}/chat_images'; + if (!await Directory(dirPath).exists()) { + await Directory(dirPath).create(); + await File('$dirPath/.nomedia').create(); + } + late File imageFile = File("$dirPath/$userID.jpg"); + imageFile.writeAsBytesSync(decodedBytes); + return imageFile; + } +} + +Future deleteFile(String userID) async { + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); + String dirPath = '${appDocumentsDirectory.path}/chat_images'; + late File imageFile = File('$dirPath/$userID.jpg'); + if (await imageFile.exists()) { + await imageFile.delete(); + } +} + +Future downChatMedia(Uint8List bytes, String ext) async { + String dir = (await getApplicationDocumentsDirectory()).path; + File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); + await file.writeAsBytes(bytes); + return file.path; +} + +void setMsgTune() async { + JustAudio.AudioPlayer player = JustAudio.AudioPlayer(); + await player.setVolume(1.0); + String audioAsset = ""; + if (Platform.isAndroid) { + audioAsset = "assets/audio/pulse_tone_android.mp3"; + } else { + audioAsset = "assets/audio/pulse_tune_ios.caf"; + } + try { + await player.setAsset(audioAsset); + await player.load(); + player.play(); + } catch (e) { + print("Error: $e"); + } +} + +// Future getChatMedia(BuildContext context, {required String fileName, required String fileTypeName, required int fileTypeID, required int fileSource}) async { +// Utils.showLoading(context); +// if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 || fileTypeID == 2 || fileTypeID == 16) { +// Uint8List encodedString = await ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: getFileTypeDescription(fileTypeName), fileSource: fileSource); +// try { +// String path = await downChatMedia(encodedString, fileTypeName ?? ""); +// Utils.hideLoading(context); +// OpenFilex.open(path); +// } catch (e) { +// Utils.showToast("Cannot open file."); +// } +// } +// } + +// void onNewChatConversion(List? params) { +// dynamic items = params!.toList(); +// chatUConvCounter = items[0]["singleChatCount"] ?? 0; +// notifyListeners(); +// } + +Future invokeChatCounter({required int userId}) async { + await chatHubConnection!.invoke("GetChatCounversationCount", args: [userId]); + return ""; +} + +void userTypingInvoke({required int currentUser, required int reciptUser}) async { + await chatHubConnection!.invoke("UserTypingAsync", args: [reciptUser, currentUser]); +} + +// void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async { +// var data = json.decode(json.encode(groupDetails.groupUserList)); +// await chatHubConnection.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]); +// } + +//////// Audio Recoding Work //////////////////// + +// Future initAudio({required int receiverId}) async { +// // final dir = Directory((Platform.isAndroid +// // ? await getExternalStorageDirectory() //FOR ANDROID +// // : await getApplicationSupportDirectory() //FOR IOS +// // )! +// appDirectory = await getApplicationDocumentsDirectory(); +// String dirPath = '${appDirectory.path}/chat_audios'; +// if (!await Directory(dirPath).exists()) { +// await Directory(dirPath).create(); +// await File('$dirPath/.nomedia').create(); +// } +// path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; +// recorderController = RecorderController() +// ..androidEncoder = AndroidEncoder.aac +// ..androidOutputFormat = AndroidOutputFormat.mpeg4 +// ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC +// ..sampleRate = 6000 +// ..updateFrequency = const Duration(milliseconds: 100) +// ..bitRate = 18000; +// playerController = PlayerController(); +// } + +// void disposeAudio() { +// isRecoding = false; +// isPlaying = false; +// isPause = false; +// isVoiceMsg = false; +// recorderController.dispose(); +// playerController.dispose(); +// } + +// void startRecoding(BuildContext context) async { +// await Permission.microphone.request().then((PermissionStatus status) { +// if (status.isPermanentlyDenied) { +// Utils.confirmDialog( +// context, +// "The app needs microphone access to be able to record audio.", +// onTap: () { +// Navigator.of(context).pop(); +// openAppSettings(); +// }, +// ); +// } else if (status.isDenied) { +// Utils.confirmDialog( +// context, +// "The app needs microphone access to be able to record audio.", +// onTap: () { +// Navigator.of(context).pop(); +// openAppSettings(); +// }, +// ); +// } else if (status.isGranted) { +// sRecoding(); +// } else { +// startRecoding(context); +// } +// }); +// } +// +// void sRecoding() async { +// isVoiceMsg = true; +// recorderController.reset(); +// await recorderController.record(path: path); +// _recodeDuration = 0; +// _startTimer(); +// isRecoding = !isRecoding; +// notifyListeners(); +// } +// +// Future _startTimer() async { +// _timer?.cancel(); +// _timer = Timer.periodic(const Duration(seconds: 1), (Timer t) async { +// _recodeDuration++; +// if (_recodeDuration <= 59) { +// applyCounter(); +// } else { +// pauseRecoding(); +// } +// }); +// } +// +// void applyCounter() { +// buildTimer(); +// notifyListeners(); +// } +// +// Future pauseRecoding() async { +// isPause = true; +// isPlaying = true; +// recorderController.pause(); +// path = await recorderController.stop(false); +// File file = File(path!); +// file.readAsBytesSync(); +// path = file.path; +// await playerController.preparePlayer(path: file.path, volume: 1.0); +// _timer?.cancel(); +// notifyListeners(); +// } +// +// Future deleteRecoding() async { +// _recodeDuration = 0; +// _timer?.cancel(); +// if (path == null) { +// path = await recorderController.stop(true); +// } else { +// await recorderController.stop(true); +// } +// if (path != null && path!.isNotEmpty) { +// File delFile = File(path!); +// double fileSizeInKB = delFile.lengthSync() / 1024; +// double fileSizeInMB = fileSizeInKB / 1024; +// if (kDebugMode) { +// debugPrint("Deleted file size: ${delFile.lengthSync()}"); +// debugPrint("Deleted file size in KB: " + fileSizeInKB.toString()); +// debugPrint("Deleted file size in MB: " + fileSizeInMB.toString()); +// } +// if (await delFile.exists()) { +// delFile.delete(); +// } +// isPause = false; +// isRecoding = false; +// isPlaying = false; +// isVoiceMsg = false; +// notifyListeners(); +// } +// } +// +// String buildTimer() { +// String minutes = _formatNum(_recodeDuration ~/ 60); +// String seconds = _formatNum(_recodeDuration % 60); +// return '$minutes : $seconds'; +// } + +String _formatNum(int number) { + String numberStr = number.toString(); + if (number < 10) { + numberStr = '0' + numberStr; + } + return numberStr; +} + +Future downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async { + File file; + try { + String dirPath = '${(await getApplicationDocumentsDirectory()).path}/chat_audios'; + if (!await Directory(dirPath).exists()) { + await Directory(dirPath).create(); + await File('$dirPath/.nomedia').create(); + } + file = File("$dirPath/${data.currentUserId}-${data.targetUserId}-${DateTime.now().microsecondsSinceEpoch}" + ext); + await file.writeAsBytes(bytes); + } catch (e) { + if (kDebugMode) { + print(e); + } + file = File(""); + } + return file; +} + +// void scrollToMsg(SingleUserChatModel data) { +// if (data.userChatReplyResponse != null && data.userChatReplyResponse!.userChatHistoryId != null) { +// int index = userChatHistory.indexWhere((SingleUserChatModel element) => element.userChatHistoryId == data.userChatReplyResponse!.userChatHistoryId); +// if (index >= 1) { +// double contentSize = scrollController.position.viewportDimension + scrollController.position.maxScrollExtent; +// double target = contentSize * index / userChatHistory.length; +// scrollController.position.animateTo( +// target, +// duration: const Duration(seconds: 1), +// curve: Curves.easeInOut, +// ); +// } +// } +// } + +// +// Future getTeamMembers() async { +// teamMembersList = []; +// isLoading = true; +// if (AppState().getemployeeSubordinatesList.isNotEmpty) { +// getEmployeeSubordinatesList = AppState().getemployeeSubordinatesList; +// for (GetEmployeeSubordinatesList element in getEmployeeSubordinatesList) { +// if (element.eMPLOYEEEMAILADDRESS != null) { +// if (element.eMPLOYEEEMAILADDRESS!.isNotEmpty) { +// teamMembersList.add( +// ChatUser( +// id: int.parse(element.eMPLOYEENUMBER!), +// email: element.eMPLOYEEEMAILADDRESS, +// userName: element.eMPLOYEENAME, +// phone: element.eMPLOYEEMOBILENUMBER, +// userStatus: 0, +// unreadMessageCount: 0, +// isFav: false, +// isTyping: false, +// isImageLoading: false, +// image: element.eMPLOYEEIMAGE ?? "", +// isImageLoaded: element.eMPLOYEEIMAGE == null ? false : true, +// userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null ? null : await downloadImageLocal(element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), +// ), +// ); +// } +// } +// } +// } else { +// getEmployeeSubordinatesList = await MyTeamApiClient().getEmployeeSubordinates("", "", ""); +// AppState().setemployeeSubordinatesList = getEmployeeSubordinatesList; +// for (GetEmployeeSubordinatesList element in getEmployeeSubordinatesList) { +// if (element.eMPLOYEEEMAILADDRESS != null) { +// if (element.eMPLOYEEEMAILADDRESS!.isNotEmpty) { +// teamMembersList.add( +// ChatUser( +// id: int.parse(element.eMPLOYEENUMBER!), +// email: element.eMPLOYEEEMAILADDRESS, +// userName: element.eMPLOYEENAME, +// phone: element.eMPLOYEEMOBILENUMBER, +// userStatus: 0, +// unreadMessageCount: 0, +// isFav: false, +// isTyping: false, +// isImageLoading: false, +// image: element.eMPLOYEEIMAGE ?? "", +// isImageLoaded: element.eMPLOYEEIMAGE == null ? false : true, +// userLocalDownlaodedImage: element.eMPLOYEEIMAGE == null ? null : await downloadImageLocal(element.eMPLOYEEIMAGE ?? "", element.eMPLOYEENUMBER!), +// ), +// ); +// } +// } +// } +// } +// +// for (ChatUser user in searchedChats!) { +// for (ChatUser teamUser in teamMembersList!) { +// if (user.id == teamUser.id) { +// teamUser.userStatus = user.userStatus; +// } +// } +// } +// +// isLoading = false; +// notifyListeners(); +// } + +// void inputBoxDirection(String val) { +// if (val.isNotEmpty) { +// isTextMsg = true; +// } else { +// isTextMsg = false; +// } +// msgText = val; +// notifyListeners(); +// } +// +// void onDirectionChange(bool val) { +// isRTL = val; +// notifyListeners(); +// } + +Material.TextDirection getTextDirection(String v) { + String str = v.trim(); + if (str.isEmpty) return Material.TextDirection.ltr; + int firstUnit = str.codeUnitAt(0); + if (firstUnit > 0x0600 && firstUnit < 0x06FF || + firstUnit > 0x0750 && firstUnit < 0x077F || + firstUnit > 0x07C0 && firstUnit < 0x07EA || + firstUnit > 0x0840 && firstUnit < 0x085B || + firstUnit > 0x08A0 && firstUnit < 0x08B4 || + firstUnit > 0x08E3 && firstUnit < 0x08FF || + firstUnit > 0xFB50 && firstUnit < 0xFBB1 || + firstUnit > 0xFBD3 && firstUnit < 0xFD3D || + firstUnit > 0xFD50 && firstUnit < 0xFD8F || + firstUnit > 0xFD92 && firstUnit < 0xFDC7 || + firstUnit > 0xFDF0 && firstUnit < 0xFDFC || + firstUnit > 0xFE70 && firstUnit < 0xFE74 || + firstUnit > 0xFE76 && firstUnit < 0xFEFC || + firstUnit > 0x10800 && firstUnit < 0x10805 || + firstUnit > 0x1B000 && firstUnit < 0x1B0FF || + firstUnit > 0x1D165 && firstUnit < 0x1D169 || + firstUnit > 0x1D16D && firstUnit < 0x1D172 || + firstUnit > 0x1D17B && firstUnit < 0x1D182 || + firstUnit > 0x1D185 && firstUnit < 0x1D18B || + firstUnit > 0x1D1AA && firstUnit < 0x1D1AD || + firstUnit > 0x1D242 && firstUnit < 0x1D244) { + return Material.TextDirection.rtl; + } + return Material.TextDirection.ltr; +} + +// void openChatByNoti(BuildContext context) async { +// SingleUserChatModel nUser = SingleUserChatModel(); +// Utils.saveStringFromPrefs("isAppOpendByChat", "false"); +// if (await Utils.getStringFromPrefs("notificationData") != "null") { +// nUser = SingleUserChatModel.fromJson(jsonDecode(await Utils.getStringFromPrefs("notificationData"))); +// Utils.saveStringFromPrefs("notificationData", "null"); +// Future.delayed(const Duration(seconds: 2)); +// for (ChatUser user in searchedChats!) { +// if (user.id == nUser.targetUserId) { +// Navigator.pushNamed(context, AppRoutes.chatDetailed, arguments: ChatDetailedScreenParams(user, false)); +// return; +// } +// } +// } +// Utils.saveStringFromPrefs("notificationData", "null"); +// } + +//group chat functions added here + +// void filterGroups(String value) async { +// // filter function added here. +// List tmp = []; +// if (value.isEmpty || value == "") { +// tmp = userGroups.groupresponse!; +// } else { +// for (groups.GroupResponse element in uGroups!) { +// if (element.groupName!.toLowerCase().contains(value.toLowerCase())) { +// tmp.add(element); +// } +// } +// } +// uGroups = tmp; +// notifyListeners(); +// } + +// Future deleteGroup(GroupResponse groupDetails) async { +// isLoading = true; +// await ChatApiClient().deleteGroup(groupDetails.groupId); +// userGroups = await ChatApiClient().getGroupsByUserId(); +// uGroups = userGroups.groupresponse; +// isLoading = false; +// notifyListeners(); +// } +// +// Future getGroupChatHistory(groups.GroupResponse groupDetails) async { +// isLoading = true; +// groupChatHistory = await ChatApiClient().getGroupChatHistory(groupDetails.groupId, groupDetails.groupUserList as List); +// +// isLoading = false; +// +// notifyListeners(); +// } +// +// void updateGroupAdmin(int? groupId, List groupUserList) async { +// isLoading = true; +// await ChatApiClient().updateGroupAdmin(groupId, groupUserList); +// isLoading = false; +// notifyListeners(); +// } + +// Future addGroupAndUsers(createGroup.CreateGroupRequest request) async { +// isLoading = true; +// var groups = await ChatApiClient().addGroupAndUsers(request); +// userGroups.groupresponse!.add(GroupResponse.fromJson(json.decode(groups)['response'])); +// +// isLoading = false; +// notifyListeners(); +// } +// +// Future updateGroupAndUsers(createGroup.CreateGroupRequest request) async { +// isLoading = true; +// await ChatApiClient().updateGroupAndUsers(request); +// userGroups = await ChatApiClient().getGroupsByUserId(); +// uGroups = userGroups.groupresponse; +// isLoading = false; +// notifyListeners(); +// } diff --git a/lib/modules/cx_module/chat/chat_rooms_page.dart b/lib/modules/cx_module/chat/chat_rooms_page.dart new file mode 100644 index 00000000..15540eaa --- /dev/null +++ b/lib/modules/cx_module/chat/chat_rooms_page.dart @@ -0,0 +1,106 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; + +class ChatRoomsPage extends StatefulWidget { + ChatRoomsPage({Key? key}) : super(key: key); + + @override + _ChatPageState createState() { + return _ChatPageState(); + } +} + +class _ChatPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.neutral100, + appBar: const DefaultAppBar(title: "Chats"), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemBuilder: (cxt, index) => Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Request No. : 432212", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50, fontWeight: FontWeight.w600), + ), + Text( + "Asset No. : HMG212", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + "Request No. : 432212", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + 6.height, + Text( + "Mon 27 Oct, 2:00 PM", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ], + ).expanded, + getStatus(), + 8.width, + const Icon( + Icons.arrow_forward_ios_rounded, + size: 12, + color: AppColor.black20, + ), + ], + ).paddingOnly(top: 4, bottom: 4).onPress(() { + // Navigator.push(context, CupertinoPageRoute(builder: (context) => ChatPage())); + return; + }), + separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), + itemCount: 10) + .toShadowContainer(context, borderRadius: 20, showShadow: false, padding: 12))); + } + + Widget getStatus() { + Color color = const Color(0xff3B755C); + return Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: const Color(0xff62BE96).withOpacity(.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + "In Progress", + maxLines: 1, + style: AppTextStyles.tinyFont2.copyWith(color: color), + ), + ); + } +} diff --git a/lib/modules/cx_module/chat/chat_widget.dart b/lib/modules/cx_module/chat/chat_widget.dart new file mode 100644 index 00000000..9d7c8a36 --- /dev/null +++ b/lib/modules/cx_module/chat/chat_widget.dart @@ -0,0 +1,97 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart'; + +import 'chat_page.dart'; +import 'chat_provider.dart'; + +class ChatWidget extends StatefulWidget { + int moduleId; + int requestId; + String? assigneeEmployeeNumber; + String? myLoginUserID; + String? contactEmployeeINumber; + bool isReadOnly; + bool isShow; + String title; + + ChatWidget( + {Key? key, + this.isReadOnly = false, + this.title = "Chat", + required this.moduleId, + required this.requestId, + this.assigneeEmployeeNumber, + this.myLoginUserID, + this.contactEmployeeINumber, + this.isShow = false}) + : super(key: key); + + @override + _ChatWidgetState createState() { + return _ChatWidgetState(); + } +} + +class _ChatWidgetState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).reset(); + getChatToken(); + } + + void getChatToken() { + // ChatProvider cProvider = Provider.of(context, listen: false); + // if (cProvider.chatLoginResponse != null && cProvider.referenceID == widget.requestId) return; + // // String assigneeEmployeeNumber = Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; + // String myEmployeeId = widget.myLoginUserID; + // + // String receiver = context.userProvider.isNurse ? widget.assigneeEmployeeNumber : widget.myEmployeeID; + + ChatProvider cProvider = Provider.of(context, listen: false); + if (cProvider.chatLoginResponse != null && cProvider.referenceID == widget.requestId) return; + String assigneeEmployeeNumber = widget.assigneeEmployeeNumber ?? Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; + String myEmployeeId = widget.myLoginUserID ?? context.userProvider.user!.username!; + + String receiver = context.userProvider.isNurse + ? assigneeEmployeeNumber + : (context.userProvider.isEngineer + ? (widget.contactEmployeeINumber ?? Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId!) + : ""); + + cProvider.getUserAutoLoginTokenSilent(widget.moduleId, widget.requestId, widget.title, myEmployeeId, receiver, isMounted: mounted); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (pContext, requestProvider, _) { + return IconButton( + icon: const Icon(Icons.chat_bubble), + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ChatPage( + moduleId: widget.moduleId, + requestId: widget.requestId, + title: widget.title, + readOnly: widget.isReadOnly, + assigneeEmployeeNumber: widget.assigneeEmployeeNumber, + contactEmployeeINumber: widget.contactEmployeeINumber, + myLoginUserID: widget.myLoginUserID, + ))); + }, + ).toShimmer(context: context, isShow: requestProvider.chatLoginTokenLoading, radius: 30, height: 30, width: 30); + }); + ; + } +} diff --git a/lib/modules/cx_module/chat/helper/chat_audio_player.dart b/lib/modules/cx_module/chat/helper/chat_audio_player.dart new file mode 100644 index 00000000..f32fcc06 --- /dev/null +++ b/lib/modules/cx_module/chat/helper/chat_audio_player.dart @@ -0,0 +1,138 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:audio_waveforms/audio_waveforms.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cx_module/chat/api_client.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart'; +import 'package:test_sa/modules/cx_module/chat/model/get_single_user_chat_list_model.dart'; + +import '../../../../new_views/app_style/app_color.dart'; + +class ChatAudioPlayer extends StatefulWidget { + String downloadURl; + FileTypeResponse file; + bool isLocalFile; + + ChatAudioPlayer(this.file, this.downloadURl, {Key? key, this.isLocalFile = false}) : super(key: key); + + @override + _ChatAudioPlayerState createState() { + return _ChatAudioPlayerState(); + } +} + +class _ChatAudioPlayerState extends State { + bool downloading = false; + + PlayerController playerController = PlayerController(); + + @override + void initState() { + super.initState(); + checkFileInLocalStorage(); + } + + File? audioFile; + List waveformData = []; + + void checkFileInLocalStorage() async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = '${tempDir.path}/${widget.file.fileName}'; + File tempFile = File(tempPath); + bool exists = await tempFile.exists(); + if (exists) { + audioFile = tempFile; + playerController = PlayerController(); + await playerController.preparePlayer(path: tempPath); + waveformData = await playerController.extractWaveformData(path: tempPath); + } else { + downloadFile(); + } + } + + void downloadFile() async { + downloading = true; + setState(() {}); + try { + // Uint8List list = await ChatApiClient().downloadFileWithHttp(widget.downloadURl, fileName: widget.file.fileName, fileTypeDescription: widget.file.fileTypeDescription, fileSource: widget.file.fileTypeId!); + // audioFile = File.fromRawPath(list); + + audioFile = + await ChatApiClient().downloadFileWithHttp(widget.downloadURl, fileName: widget.file.fileName, fileTypeDescription: widget.file.fileTypeDescription, fileSource: widget.file.fileTypeId!); + + playerController = PlayerController(); + await playerController.preparePlayer(path: audioFile!.path); + waveformData = await playerController.extractWaveformData(path: audioFile!.path); + } catch (ex) { + print(ex); + audioFile = null; + } + downloading = false; + setState(() {}); + } + + @override + void dispose() { + playerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 56, + child: Row( + children: [ + if (downloading) + const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ) + else if (playerController.playerState == PlayerState.playing) + IconButton( + onPressed: () async { + await playerController.pausePlayer(); + await playerController.stopPlayer(); + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: const Icon(Icons.stop_circle_outlined, size: 20), + constraints: const BoxConstraints(), + ) + else + IconButton( + onPressed: () async { + await playerController.startPlayer(); + setState(() {}); + }, + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded + ), + icon: const Icon(Icons.play_circle_fill_rounded, size: 20), + constraints: const BoxConstraints(), + ), + AudioFileWaveforms( + playerController: playerController, + waveformData: waveformData, + enableSeekGesture: false, + continuousWaveform: false, + waveformType: WaveformType.long, + playerWaveStyle: const PlayerWaveStyle( + fixedWaveColor: AppColor.neutral50, + liveWaveColor: AppColor.primary10, + showSeekLine: true, + ), + size: Size(MediaQuery.of(context).size.width, 56.0), + ).expanded, + ], + ), + ); + } +} diff --git a/lib/modules/cx_module/chat/helper/chat_file_picker.dart b/lib/modules/cx_module/chat/helper/chat_file_picker.dart new file mode 100644 index 00000000..1070e3db --- /dev/null +++ b/lib/modules/cx_module/chat/helper/chat_file_picker.dart @@ -0,0 +1,436 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; + +class ChatFilePicker extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Attach File".heading4(context), + 12.height, + GridView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), + children: [ + gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() async { + await fromMediaPicker(context, ImageSource.camera); + }), + gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() async { + await fromMediaPicker(context, ImageSource.gallery); + }), + gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { + await fromFilePicker(context); + }), + ], + ), + 12.height, + ], + ).paddingAll(21); + } + + fromMediaPicker(BuildContext context, ImageSource imageSource) async { + XFile? pickedFile = await ImagePicker().pickImage(source: imageSource, imageQuality: 70, maxWidth: 800, maxHeight: 800); + if (pickedFile != null) { + CroppedFile? croppedFile = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + // aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1), + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'ATOMS', + toolbarColor: Colors.white, + toolbarWidgetColor: context.settingProvider.theme == "dark" ? AppColor.neutral10 : AppColor.neutral50, + initAspectRatio: CropAspectRatioPreset.square, + lockAspectRatio: false, + ), + IOSUiSettings(title: 'ATOMS'), + ], + ); + if (croppedFile != null) { + Navigator.pop(context, File(croppedFile.path)); + return; + } + } + Navigator.pop(context); + } + + fromFilePicker(BuildContext context) async { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx', 'txt'], + ); + if ((result?.paths ?? []).isNotEmpty) { + Navigator.pop(context, File(result!.paths.first!)); + } else { + Navigator.pop(context); + } + } + + Widget gridItem(IconData iconData, String title) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xffF1F1F1), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(iconData, color: const Color(0xff7D859A), size: 36), + Text( + title, + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ], + ), + ); + } + +// static Future selectFile(BuildContext context) async { +// ImageSource source = (await showModalBottomSheet( +// context: context, +// shape: const RoundedRectangleBorder( +// borderRadius: BorderRadius.vertical( +// top: Radius.circular(20), +// ), +// ), +// clipBehavior: Clip.antiAliasWithSaveLayer, +// builder: (BuildContext context) => ChatFilePicker())) as ImageSource; +// +// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); +// } +} + +// class AttachmentPicker extends StatefulWidget { +// final String label; +// final bool error; +// final List attachment; +// +// final bool enabled, onlyImages; +// double? buttonHeight; +// Widget? buttonIcon; +// Color? buttonColor; +// final Function(List)? onChange; +// final bool showAsGrid; +// +// AttachmentPicker( +// {Key? key, +// this.attachment = const [], +// required this.label, +// this.error = false, +// this.buttonHeight, +// this.buttonIcon, +// this.enabled = true, +// this.onlyImages = false, +// this.onChange, +// this.showAsGrid = false, +// this.buttonColor}) +// : super(key: key); +// +// @override +// State createState() => _AttachmentPickerState(); +// } +// +// class _AttachmentPickerState extends State { +// @override +// Widget build(BuildContext context) { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// AppDashedButton( +// title: widget.label, +// height: widget.buttonHeight, +// buttonColor: widget.buttonColor, +// icon: widget.buttonIcon, +// onPressed: (widget.enabled == false) +// ? () {} +// : widget.showAsGrid +// ? showFileSourceSheet +// : onFilePicker), +// 16.height, +// if (widget.attachment.isNotEmpty) +// Wrap( +// spacing: 8.toScreenWidth, +// children: List.generate( +// widget.attachment.length, +// (index) { +// File image = File(widget.attachment[index].name!); +// return MultiFilesPickerItem( +// file: image, +// enabled: widget.enabled, +// onRemoveTap: (image) { +// if (!widget.enabled) { +// return; +// } +// widget.attachment.removeAt(index); +// if (widget.onChange != null) { +// widget.onChange!(widget.attachment); +// } +// setState(() {}); +// }, +// ); +// }, +// ), +// ), +// ], +// ); +// } +// +// fromFilePicker() async { +// FilePickerResult? result = await FilePicker.platform.pickFiles( +// type: FileType.custom, +// allowMultiple: true, +// allowedExtensions: widget.onlyImages ? ['jpg', 'jpeg', 'png'] : ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx'], +// ); +// if (result != null) { +// for (var path in result.paths) { +// widget.attachment.add(GenericAttachmentModel(id: 0, name: File(path!).path)); +// } +// if (widget.onChange != null) { +// widget.onChange!(widget.attachment); +// } +// setState(() {}); +// } +// } +// +// void showFileSourceSheet() async { +// // if (widget.attachment.length >= 5) { +// // Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); +// // return; +// // } +// +// ImageSource source = (await showModalBottomSheet( +// context: context, +// shape: const RoundedRectangleBorder( +// borderRadius: BorderRadius.vertical( +// top: Radius.circular(20), +// ), +// ), +// clipBehavior: Clip.antiAliasWithSaveLayer, +// builder: (BuildContext context) => Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// "Attach File".heading4(context), +// 12.height, +// GridView( +// padding: const EdgeInsets.all(0), +// shrinkWrap: true, +// physics: const NeverScrollableScrollPhysics(), +// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), +// children: [ +// gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)), +// gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)), +// gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { +// await fromFilePicker(); +// Navigator.pop(context); +// }), +// ], +// ), +// 12.height, +// ], +// ).paddingAll(21), +// )) as ImageSource; +// +// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); +// +// if (pickedFile != null) { +// File fileImage = File(pickedFile.path); +// widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path)); +// if (widget.onChange != null) { +// widget.onChange!(widget.attachment); +// } +// setState(() {}); +// } +// } +// +// Widget gridItem(IconData iconData, String title) { +// return Container( +// padding: const EdgeInsets.all(12), +// decoration: BoxDecoration( +// color: Colors.white, +// borderRadius: BorderRadius.circular(12), +// border: Border.all(color: const Color(0xffF1F1F1), width: 1), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Icon(iconData, color: const Color(0xff7D859A), size: 36), +// Text( +// title, +// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), +// ), +// ], +// ), +// ); +// } +// +// onFilePicker() async { +// //TODO removed on request by Backend as they don't have anyissue with large number of files +// // if (widget.attachment.length >= 5) { +// // Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); +// // return; +// // } +// ImageSource? source = await showModalBottomSheet( +// context: context, +// builder: (BuildContext context) { +// Widget listCard({required String icon, required String label, required VoidCallback onTap}) { +// return Container( +// padding: const EdgeInsets.all(12), +// decoration: BoxDecoration( +// color: AppColor.background(context), +// // color: Colors.white, +// borderRadius: BorderRadius.circular(12), +// border: Border.all(color: const Color(0xffF1F1F1), width: 1), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// icon.toSvgAsset(color: AppColor.iconColor(context), width: 36, height: 36), +// // Icon(iconData, color: const Color(0xff7D859A), size: 36), +// Text( +// label, +// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), +// ), +// ], +// ), +// ).onPress(onTap); +// } +// +// return SafeArea( +// top: false, +// child: Container( +// width: double.infinity, +// color: AppColor.background(context), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// "Attach File".heading4(context), +// 12.height, +// GridView( +// padding: const EdgeInsets.all(0), +// shrinkWrap: true, +// physics: const NeverScrollableScrollPhysics(), +// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), +// children: [ +// listCard( +// icon: 'camera_icon', +// label: '${context.translation.open}\n${context.translation.camera}', +// onTap: () { +// Navigator.of(context).pop(ImageSource.camera); +// }, +// ), +// listCard( +// icon: 'gallery_icon', +// label: '${context.translation.open}\n${context.translation.gallery}', +// onTap: () { +// Navigator.of(context).pop(ImageSource.gallery); +// }, +// ), +// listCard( +// icon: 'file_icon', +// label: '${context.translation.open}\n${context.translation.files}', +// onTap: () async { +// await fromFilePicker(); +// Navigator.pop(context); +// }, +// ), +// ], +// ), +// // Container( +// // padding: const EdgeInsets.all(16.0), +// // child: Row( +// // mainAxisAlignment: MainAxisAlignment.spaceBetween, +// // children: [ +// // listCard( +// // icon: 'camera_icon', +// // label: '${context.translation.open}\n${context.translation.camera}', +// // onTap: () { +// // Navigator.of(context).pop(ImageSource.camera); +// // }, +// // ), +// // listCard( +// // icon: 'gallery_icon', +// // label: '${context.translation.open}\n${context.translation.gallery}', +// // onTap: () { +// // Navigator.of(context).pop(ImageSource.gallery); +// // }, +// // ), +// // listCard( +// // icon: 'file_icon', +// // label: '${context.translation.open}\n${context.translation.files}', +// // onTap: () async { +// // await fromFilePicker(); +// // Navigator.pop(context); +// // }, +// // ), +// // ], +// // ), +// // ), +// ], +// ).paddingAll(16), +// ), +// ); +// }, +// ); +// // ImageSource source = await showDialog( +// // context: context, +// // builder: (dialogContext) => CupertinoAlertDialog( +// // actions: [ +// // TextButton( +// // child: Text(context.translation.pickFromCamera), +// // onPressed: () { +// // Navigator.of(dialogContext).pop(ImageSource.camera); +// // }, +// // ), +// // TextButton( +// // child: Text(context.translation.pickFromGallery), +// // onPressed: () { +// // Navigator.of(dialogContext).pop(ImageSource.gallery); +// // }, +// // ), +// // TextButton( +// // child: Text(context.translation.pickFromFiles), +// // onPressed: () async { +// // await fromFilePicker(); +// // Navigator.pop(context); +// // }, +// // ), +// // ], +// // ), +// // ); +// if (source == null) return; +// +// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); +// +// if (pickedFile != null) { +// File fileImage = File(pickedFile.path); +// widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path)); +// if (widget.onChange != null) { +// widget.onChange!(widget.attachment); +// } +// setState(() {}); +// } +// +// setState(() {}); +// } +// } diff --git a/lib/modules/cx_module/chat/helper/chat_file_viewer.dart b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart new file mode 100644 index 00000000..fbd53eec --- /dev/null +++ b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart @@ -0,0 +1,137 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/chat/model/get_single_user_chat_list_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_style.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker_item.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +class ChatFileViewer extends StatelessWidget { + String downloadUrl; + FileTypeResponse fileTypeResponse; + + ChatFileViewer(this.downloadUrl, this.fileTypeResponse, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: checkFileInLocalStorage(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.waiting && snapshot.hasData) {} + + if (fileTypeResponse!.fileKind.toString().toLowerCase() == "audio") { + if (snapshot.connectionState == ConnectionState.waiting) { + return Container( + width: 48, + height: 48, + decoration: ShapeDecoration( + color: AppColor.background(context), + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: (context.isDark ? AppColor.neutral20 : AppColor.neutral30)), + borderRadius: BorderRadius.circular(32), + ), + ), + child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2).center, + ); + } + if (snapshot.data == null) { + return Text("Failed to load", style: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60)); + } + return ASoundPlayer(audio: snapshot.data!.path); + } + return Container( + width: 180, + height: 180, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.withOpacity(.5), width: 1), + image: snapshot.hasData ? DecorationImage(fit: BoxFit.contain, image: getImage(snapshot.data!)) : null, + ), + child: snapshot.connectionState == ConnectionState.waiting + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ).center + : snapshot.hasData + ? getFile(context, snapshot.data!) + : const Icon(Icons.broken_image_rounded), + ); + }); + } + + Widget getFile(BuildContext context, File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + return MaterialButton( + padding: EdgeInsets.zero, + onPressed: () async { + if (isImage) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea( + child: InteractiveViewer(child: Image.file(file)).center, + ), + ), + ), + ); + } else { + OpenFile.open(file.path); + } + // else { + // // if (!await launchUrl(Uri.parse(URLs.getFileUrl(file.path)!), mode: LaunchMode.externalApplication)) { + // // Fluttertoast.showToast(msg: "UnExpected Error with file."); + // // throw Exception('Could not launch'); + // // } + // } + }, + ); + } + + ImageProvider getImage(File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + if (isImage) { + return FileImage(file); + } + return AssetImage("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png"); + } + + Future checkFileInLocalStorage() async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = '${tempDir.path}/${fileTypeResponse.fileName}'; + File tempFile = File(tempPath); + bool exists = await tempFile.exists(); + if (exists) { + return tempFile; + } else { + return downloadFile(); + } + } + + Future downloadFile() async { + try { + return await ChatApiClient() + .downloadFileWithHttp(downloadUrl, fileName: fileTypeResponse.fileName, fileTypeDescription: fileTypeResponse.fileTypeDescription, fileSource: fileTypeResponse.fileTypeId!); + } catch (ex) { + print("downloadFile:$ex"); + return null; + } + } +} diff --git a/lib/modules/cx_module/chat/model/chat_attachment_model.dart b/lib/modules/cx_module/chat/model/chat_attachment_model.dart new file mode 100644 index 00000000..3705ae05 --- /dev/null +++ b/lib/modules/cx_module/chat/model/chat_attachment_model.dart @@ -0,0 +1,60 @@ +class ChatAttachment { + String? id; + String? originalFileName; + String? storedFileName; + String? contentType; + int? sizeBytes; + String? downloadUrl; + String? relativePath; + String? createdOn; + bool? isContextual; + String? moduleCode; + String? referenceId; + String? conversationId; + + ChatAttachment( + {this.id, + this.originalFileName, + this.storedFileName, + this.contentType, + this.sizeBytes, + this.downloadUrl, + this.relativePath, + this.createdOn, + this.isContextual, + this.moduleCode, + this.referenceId, + this.conversationId}); + + ChatAttachment.fromJson(Map json) { + id = json['id']; + originalFileName = json['originalFileName']; + storedFileName = json['storedFileName']; + contentType = json['contentType']; + sizeBytes = json['sizeBytes']; + downloadUrl = json['downloadUrl']; + relativePath = json['relativePath']; + createdOn = json['createdOn']; + isContextual = json['isContextual']; + moduleCode = json['moduleCode']; + referenceId = json['referenceId']; + conversationId = json['conversationId']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['originalFileName'] = this.originalFileName; + data['storedFileName'] = this.storedFileName; + data['contentType'] = this.contentType; + data['sizeBytes'] = this.sizeBytes; + data['downloadUrl'] = this.downloadUrl; + data['relativePath'] = this.relativePath; + data['createdOn'] = this.createdOn; + data['isContextual'] = this.isContextual; + data['moduleCode'] = this.moduleCode; + data['referenceId'] = this.referenceId; + data['conversationId'] = this.conversationId; + return data; + } +} diff --git a/lib/modules/cx_module/chat/model/chat_login_response_model.dart b/lib/modules/cx_module/chat/model/chat_login_response_model.dart new file mode 100644 index 00000000..a8612818 --- /dev/null +++ b/lib/modules/cx_module/chat/model/chat_login_response_model.dart @@ -0,0 +1,101 @@ +class ChatLoginResponse { + String? token; + int? userId; + String? userName; + int? applicationId; + int? expiresIn; + Context? context; + + ChatLoginResponse( + {this.token, + this.userId, + this.userName, + this.applicationId, + this.expiresIn, + this.context}); + + ChatLoginResponse.fromJson(Map json) { + token = json['token']; + userId = json['userId']; + userName = json['userName']; + applicationId = json['applicationId']; + expiresIn = json['expiresIn']; + context = + json['context'] != null ? new Context.fromJson(json['context']) : null; + } + + Map toJson() { + final Map data = new Map(); + data['token'] = this.token; + data['userId'] = this.userId; + data['userName'] = this.userName; + data['applicationId'] = this.applicationId; + data['expiresIn'] = this.expiresIn; + if (this.context != null) { + data['context'] = this.context!.toJson(); + } + return data; + } +} + +class Context { + String? conversationId; + String? moduleCode; + String? referenceId; + String? title; + List? deepLinks; + + Context( + {this.conversationId, + this.moduleCode, + this.referenceId, + this.title, + this.deepLinks}); + + Context.fromJson(Map json) { + conversationId = json['conversationId']; + moduleCode = json['moduleCode']; + referenceId = json['referenceId']; + title = json['title']; + if (json['deepLinks'] != null) { + deepLinks = []; + json['deepLinks'].forEach((v) { + deepLinks!.add(new DeepLinks.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['conversationId'] = this.conversationId; + data['moduleCode'] = this.moduleCode; + data['referenceId'] = this.referenceId; + data['title'] = this.title; + if (this.deepLinks != null) { + data['deepLinks'] = this.deepLinks!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class DeepLinks { + String? employeeNumber; + int? userId; + String? userName; + + DeepLinks({this.employeeNumber, this.userId, this.userName}); + + DeepLinks.fromJson(Map json) { + employeeNumber = json['employeeNumber']; + userId = json['userId']; + userName = json['userName']; + } + + Map toJson() { + final Map data = new Map(); + data['employeeNumber'] = this.employeeNumber; + data['userId'] = this.userId; + data['userName'] = this.userName; + return data; + } +} diff --git a/lib/modules/cx_module/chat/model/chat_participant_model.dart b/lib/modules/cx_module/chat/model/chat_participant_model.dart new file mode 100644 index 00000000..6ef1d4e4 --- /dev/null +++ b/lib/modules/cx_module/chat/model/chat_participant_model.dart @@ -0,0 +1,65 @@ +class ChatParticipantModel { + int? id; + String? title; + String? conversationType; + List? participants; + String? lastMessage; + String? createdAt; + + ChatParticipantModel({this.id, this.title, this.conversationType, this.participants, this.lastMessage, this.createdAt}); + + ChatParticipantModel.fromJson(Map json) { + id = json['id']; + title = json['title']; + conversationType = json['conversationType']; + if (json['participants'] != null) { + participants = []; + json['participants'].forEach((v) { + participants!.add(new Participants.fromJson(v)); + }); + } + lastMessage = json['lastMessage']; + createdAt = json['createdAt']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['title'] = this.title; + data['conversationType'] = this.conversationType; + if (this.participants != null) { + data['participants'] = this.participants!.map((v) => v.toJson()).toList(); + } + data['lastMessage'] = this.lastMessage; + data['createdAt'] = this.createdAt; + return data; + } +} + +class Participants { + String? userId; + String? userName; + String? employeeNumber; + String? role; + int? userStatus; + + Participants({this.userId, this.userName, this.employeeNumber, this.role, this.userStatus}); + + Participants.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + employeeNumber = json['employeeNumber']; + role = json['role']; + userStatus = json['userStatus']; + } + + Map toJson() { + final Map data = new Map(); + data['userId'] = this.userId; + data['userName'] = this.userName; + data['employeeNumber'] = this.employeeNumber; + data['role'] = this.role; + data['userStatus'] = this.userStatus; + return data; + } +} diff --git a/lib/modules/cx_module/chat/model/get_search_user_chat_model.dart b/lib/modules/cx_module/chat/model/get_search_user_chat_model.dart new file mode 100644 index 00000000..7c638e9e --- /dev/null +++ b/lib/modules/cx_module/chat/model/get_search_user_chat_model.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; +import 'dart:io'; + +ChatUserModel chatUserModelFromJson(String str) => ChatUserModel.fromJson(json.decode(str)); + +String chatUserModelToJson(ChatUserModel data) => json.encode(data.toJson()); + +class ChatUserModel { + ChatUserModel({ + this.response, + this.errorResponses, + }); + + List? response; + List? errorResponses; + + factory ChatUserModel.fromJson(Map json) => ChatUserModel( + response: json["response"] == null ? null : List.from(json["response"].map((x) => ChatUser.fromJson(x))), + errorResponses: json["errorResponses"] == null ? null : List.from(json["errorResponses"].map((x) => ErrorResponse.fromJson(x))), + ); + + Map toJson() => { + "response": response == null ? null : List.from(response!.map((x) => x.toJson())), + "errorResponses": errorResponses == null ? null : List.from(errorResponses!.map((x) => x.toJson())), + }; +} + +class ErrorResponse { + ErrorResponse({ + this.fieldName, + this.message, + }); + + dynamic? fieldName; + String? message; + + factory ErrorResponse.fromRawJson(String str) => ErrorResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ErrorResponse.fromJson(Map json) => ErrorResponse( + fieldName: json["fieldName"], + message: json["message"] == null ? null : json["message"], + ); + + Map toJson() => { + "fieldName": fieldName, + "message": message == null ? null : message, + }; +} + +class ChatUser { + ChatUser({ + this.id, + this.userName, + this.email, + this.phone, + this.title, + this.userStatus, + this.image, + this.unreadMessageCount, + this.userAction, + this.isPin, + this.isFav, + this.isAdmin, + this.rKey, + this.totalCount, + this.isTyping, + this.isImageLoaded, + this.isImageLoading, + this.userLocalDownlaodedImage, + this.isChecked + }); + + int? id; + String? userName; + String? email; + dynamic? phone; + String? title; + int? userStatus; + dynamic? image; + int? unreadMessageCount; + dynamic? userAction; + bool? isPin; + bool? isFav; + bool? isAdmin; + dynamic? rKey; + int? totalCount; + bool? isTyping; + bool? isImageLoaded; + bool? isImageLoading; + File? userLocalDownlaodedImage; + bool? isChecked; + factory ChatUser.fromRawJson(String str) => ChatUser.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ChatUser.fromJson(Map json) => ChatUser( + id: json["id"] == null ? null : json["id"], + userName: json["userName"] == null ? null : json["userName"], + email: json["email"] == null ? null : json["email"], + phone: json["phone"], + title: json["title"] == null ? null : json["title"], + userStatus: json["userStatus"] == null ? null : json["userStatus"], + image: json["image"], + unreadMessageCount: json["unreadMessageCount"] == null ? null : json["unreadMessageCount"], + userAction: json["userAction"], + isPin: json["isPin"] == null ? null : json["isPin"], + isFav: json["isFav"] == null ? null : json["isFav"], + isAdmin: json["isAdmin"] == null ? null : json["isAdmin"], + rKey: json["rKey"], + totalCount: json["totalCount"] == null ? null : json["totalCount"], + isTyping: false, + isImageLoaded: false, + isImageLoading: true, + userLocalDownlaodedImage: null, + isChecked: false + ); + + Map toJson() => { + "id": id == null ? null : id, + "userName": userName == null ? null : userName, + "email": email == null ? null : email, + "phone": phone, + "title": title == null ? null : title, + "userStatus": userStatus == null ? null : userStatus, + "image": image, + "unreadMessageCount": unreadMessageCount == null ? null : unreadMessageCount, + "userAction": userAction, + "isPin": isPin == null ? null : isPin, + "isFav": isFav == null ? null : isFav, + "isAdmin": isAdmin == null ? null : isAdmin, + "rKey": rKey, + "totalCount": totalCount == null ? null : totalCount, + "isChecked":isChecked + }; +} diff --git a/lib/modules/cx_module/chat/model/get_single_user_chat_list_model.dart b/lib/modules/cx_module/chat/model/get_single_user_chat_list_model.dart new file mode 100644 index 00000000..7d87aca2 --- /dev/null +++ b/lib/modules/cx_module/chat/model/get_single_user_chat_list_model.dart @@ -0,0 +1,210 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; +import 'package:just_audio/just_audio.dart'; + +List singleUserChatModelFromJson(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); + +String singleUserChatModelToJson(List data) => json.encode(List.from(data.map((x) => x.toJson()))); + +class SingleUserChatModel { + SingleUserChatModel( + {this.userChatHistoryId, + this.userChatHistoryLineId, + this.contant, + this.contantNo, + this.currentUserId, + this.currentUserName, + this.targetUserId, + this.targetUserName, + this.encryptedTargetUserId, + this.encryptedTargetUserName, + this.currentUserEmail, + this.targetUserEmail, + this.chatEventId, + this.fileTypeId, + this.isSeen, + this.isDelivered, + this.createdDate, + this.chatSource, + this.conversationId, + this.downloadUrl, + this.fileTypeResponse, + this.userChatReplyResponse, + this.isReplied, + this.isImageLoaded, + this.image, + this.voice, + this.voiceController}); + + int? userChatHistoryId; + int? userChatHistoryLineId; + String? contant; + String? contantNo; + String? currentUserId; + String? currentUserName; + String? currentUserEmail; + String? targetUserId; + String? targetUserName; + String? targetUserEmail; + String? encryptedTargetUserId; + String? encryptedTargetUserName; + int? chatEventId; + dynamic? fileTypeId; + bool? isSeen; + bool? isDelivered; + DateTime? createdDate; + int? chatSource; + String? conversationId; + String? downloadUrl; + FileTypeResponse? fileTypeResponse; + UserChatReplyResponse? userChatReplyResponse; + bool? isReplied; + bool? isImageLoaded; + Uint8List? image; + File? voice; + AudioPlayer? voiceController; + + factory SingleUserChatModel.fromJson(Map json) => SingleUserChatModel( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + currentUserId: json["currentUserId"] == null ? null : json["currentUserId"].toString(), + currentUserName: json["currentUserName"] == null ? null : json["currentUserName"], + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"].toString(), + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + targetUserEmail: json["targetUserEmail"] == null ? null : json["targetUserEmail"], + currentUserEmail: json["currentUserEmail"] == null ? null : json["currentUserEmail"], + encryptedTargetUserId: json["encryptedTargetUserId"] == null ? null : json["encryptedTargetUserId"], + encryptedTargetUserName: json["encryptedTargetUserName"] == null ? null : json["encryptedTargetUserName"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + fileTypeId: json["fileTypeId"], + isSeen: json["isSeen"] == null ? null : json["isSeen"], + isDelivered: json["isDelivered"] == null ? null : json["isDelivered"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + chatSource: json["chatSource"] == null ? null : json["chatSource"], + conversationId: json["conversationId"] == null ? null : json["conversationId"], + downloadUrl: json["downloadUrl"] == null ? null : json["downloadUrl"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), + isReplied: false, + isImageLoaded: false, + image: null, + voice: null, + voiceController: json["fileTypeId"] == 13 ? AudioPlayer() : null); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, + "userChatHistoryLineId": userChatHistoryLineId == null ? null : userChatHistoryLineId, + "contant": contant == null ? null : contant, + "contantNo": contantNo == null ? null : contantNo, + "currentUserId": currentUserId == null ? null : currentUserId, + "currentUserName": currentUserName == null ? null : currentUserName, + "targetUserId": targetUserId == null ? null : targetUserId, + "targetUserName": targetUserName == null ? null : targetUserName, + "encryptedTargetUserId": encryptedTargetUserId == null ? null : encryptedTargetUserId, + "encryptedTargetUserName": encryptedTargetUserName == null ? null : encryptedTargetUserName, + "currentUserEmail": currentUserEmail == null ? null : currentUserEmail, + "targetUserEmail": targetUserEmail == null ? null : targetUserEmail, + "chatEventId": chatEventId == null ? null : chatEventId, + "fileTypeId": fileTypeId, + "isSeen": isSeen == null ? null : isSeen, + "isDelivered": isDelivered == null ? null : isDelivered, + "createdDate": createdDate == null ? null : createdDate!.toIso8601String(), + "chatSource": chatSource == null ? null : chatSource, + "conversationId": conversationId == null ? null : conversationId, + "downloadUrl": downloadUrl == null ? null : downloadUrl, + "fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(), + "userChatReplyResponse": userChatReplyResponse == null ? null : userChatReplyResponse!.toJson(), + }; +} + +class FileTypeResponse { + FileTypeResponse({ + this.fileTypeId, + this.fileTypeName, + this.fileTypeDescription, + this.fileKind, + this.fileName, + }); + + int? fileTypeId; + dynamic fileTypeName; + dynamic fileTypeDescription; + dynamic fileKind; + dynamic fileName; + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"] == null ? null : json["fileTypeId"], + fileTypeName: json["fileTypeName"], + fileTypeDescription: json["fileTypeDescription"], + fileKind: json["fileKind"], + fileName: json["fileName"], + ); + + Map toJson() => { + "fileTypeId": fileTypeId == null ? null : fileTypeId, + "fileTypeName": fileTypeName, + "fileTypeDescription": fileTypeDescription, + "fileKind": fileKind, + "fileName": fileName, + }; +} + +class UserChatReplyResponse { + UserChatReplyResponse( + {this.userChatHistoryId, + this.chatEventId, + this.contant, + this.contantNo, + this.fileTypeId, + this.createdDate, + this.targetUserId, + this.targetUserName, + this.fileTypeResponse, + this.isImageLoaded, + this.image, + this.voice}); + + int? userChatHistoryId; + int? chatEventId; + String? contant; + String? contantNo; + dynamic? fileTypeId; + DateTime? createdDate; + int? targetUserId; + String? targetUserName; + FileTypeResponse? fileTypeResponse; + bool? isImageLoaded; + Uint8List? image; + Uint8List? voice; + + factory UserChatReplyResponse.fromJson(Map json) => UserChatReplyResponse( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + fileTypeId: json["fileTypeId"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + isImageLoaded: false, + image: null, + voice: null, + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, + "chatEventId": chatEventId == null ? null : chatEventId, + "contant": contant == null ? null : contant, + "contantNo": contantNo == null ? null : contantNo, + "fileTypeId": fileTypeId, + "createdDate": createdDate == null ? null : createdDate!.toIso8601String(), + "targetUserId": targetUserId == null ? null : targetUserId, + "targetUserName": targetUserName == null ? null : targetUserName, + "fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(), + }; +} diff --git a/lib/modules/cx_module/chat/model/get_user_login_token_model.dart b/lib/modules/cx_module/chat/model/get_user_login_token_model.dart new file mode 100644 index 00000000..f0620c3b --- /dev/null +++ b/lib/modules/cx_module/chat/model/get_user_login_token_model.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; + +UserAutoLoginModel userAutoLoginModelFromJson(String str) => UserAutoLoginModel.fromJson(json.decode(str)); + +String userAutoLoginModelToJson(UserAutoLoginModel data) => json.encode(data.toJson()); + +class UserAutoLoginModel { + UserAutoLoginModel({this.response, this.errorResponses, this.StatusCode}); + + Response? response; + List? errorResponses; + int? StatusCode; + + factory UserAutoLoginModel.fromJson(Map json) => UserAutoLoginModel( + response: json["response"] == null ? null : Response.fromJson(json["response"]), + StatusCode: json["StatusCode"], + errorResponses: json["errorResponses"] == null ? null : List.from(json["errorResponses"].map((x) => ErrorResponse.fromJson(x))), + ); + + Map toJson() => { + "response": response == null ? null : response!.toJson(), + "StatusCode": StatusCode, + "errorResponses": errorResponses == null ? null : List.from(errorResponses!.map((x) => x.toJson())), + }; +} + +class Response { + Response({ + this.id, + this.userName, + this.email, + this.phone, + this.title, + this.token, + this.isDomainUser, + this.isActiveCode, + this.encryptedUserId, + this.encryptedUserName, + }); + + int? id; + String? userName; + String? email; + String? phone; + String? title; + String? token; + bool? isDomainUser; + bool? isActiveCode; + String? encryptedUserId; + String? encryptedUserName; + + factory Response.fromJson(Map json) => Response( + id: json["id"] == null ? null : json["id"], + userName: json["userName"] == null ? null : json["userName"], + email: json["email"] == null ? null : json["email"], + phone: json["phone"] == null ? null : json["phone"], + title: json["title"] == null ? null : json["title"], + token: json["token"] == null ? null : json["token"], + isDomainUser: json["isDomainUser"] == null ? null : json["isDomainUser"], + isActiveCode: json["isActiveCode"] == null ? null : json["isActiveCode"], + encryptedUserId: json["encryptedUserId"] == null ? null : json["encryptedUserId"], + encryptedUserName: json["encryptedUserName"] == null ? null : json["encryptedUserName"], + ); + + Map toJson() => { + "id": id == null ? null : id, + "userName": userName == null ? null : userName, + "email": email == null ? null : email, + "phone": phone == null ? null : phone, + "title": title == null ? null : title, + "token": token == null ? null : token, + "isDomainUser": isDomainUser == null ? null : isDomainUser, + "isActiveCode": isActiveCode == null ? null : isActiveCode, + "encryptedUserId": encryptedUserId == null ? null : encryptedUserId, + "encryptedUserName": encryptedUserName == null ? null : encryptedUserName, + }; +} + +class ErrorResponse { + ErrorResponse({ + this.fieldName, + this.message, + }); + + String? fieldName; + String? message; + + factory ErrorResponse.fromJson(Map json) => ErrorResponse( + fieldName: json["fieldName"] == null ? null : json["fieldName"], + message: json["message"] == null ? null : json["message"], + ); + + Map toJson() => { + "fieldName": fieldName == null ? null : fieldName, + "message": message == null ? null : message, + }; +} diff --git a/lib/modules/cx_module/chat/model/user_chat_history_model.dart b/lib/modules/cx_module/chat/model/user_chat_history_model.dart new file mode 100644 index 00000000..7ec1e9c3 --- /dev/null +++ b/lib/modules/cx_module/chat/model/user_chat_history_model.dart @@ -0,0 +1,224 @@ +// class UserChatHistoryModel { +// List? response; +// bool? isSuccess; +// List? onlineUserConnId; +// +// UserChatHistoryModel({this.response, this.isSuccess, this.onlineUserConnId}); +// +// UserChatHistoryModel.fromJson(Map json) { +// if (json['response'] != null) { +// response = []; +// json['response'].forEach((v) { +// response!.add(new ChatResponse.fromJson(v)); +// }); +// } +// isSuccess = json['isSuccess']; +// onlineUserConnId = json['onlineUserConnId'].cast(); +// } +// +// Map toJson() { +// final Map data = new Map(); +// if (this.response != null) { +// data['response'] = this.response!.map((v) => v.toJson()).toList(); +// } +// data['isSuccess'] = this.isSuccess; +// data['onlineUserConnId'] = this.onlineUserConnId; +// return data; +// } +// } +// +// class ChatResponse { +// int? id; +// int? conversationId; +// String? userId; +// int? userIdInt; +// String? userName; +// String? content; +// String? messageType; +// String? createdAt; +// +// ChatResponse({this.id, this.conversationId, this.userId, this.userIdInt, this.userName, this.content, this.messageType, this.createdAt}); +// +// ChatResponse.fromJson(Map json) { +// id = json['id']; +// conversationId = json['conversationId']; +// userId = json['userId']; +// userIdInt = json['userIdInt']; +// userName = json['userName']; +// content = json['content']; +// messageType = json['messageType']; +// createdAt = json['createdAt']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['id'] = this.id; +// data['conversationId'] = this.conversationId; +// data['userId'] = this.userId; +// data['userIdInt'] = this.userIdInt; +// data['userName'] = this.userName; +// data['content'] = this.content; +// data['messageType'] = this.messageType; +// data['createdAt'] = this.createdAt; +// return data; +// } +// } +// +// class ChatHistoryResponse { +// int? userChatHistoryId; +// int? userChatHistoryLineId; +// String? contant; +// String? contantNo; +// String? currentUserId; +// String? currentEmployeeNumber; +// String? currentUserName; +// String? currentUserEmail; +// String? currentFullName; +// String? targetUserId; +// String? targetEmployeeNumber; +// String? targetUserName; +// String? targetUserEmail; +// String? targetFullName; +// String? encryptedTargetUserId; +// String? encryptedTargetUserName; +// int? chatEventId; +// String? fileTypeId; +// bool? isSeen; +// bool? isDelivered; +// String? createdDate; +// int? chatSource; +// String? conversationId; +// FileTypeResponse? fileTypeResponse; +// String? userChatReplyResponse; +// String? deviceToken; +// bool? isHuaweiDevice; +// String? platform; +// String? voipToken; +// +// ChatHistoryResponse( +// {this.userChatHistoryId, +// this.userChatHistoryLineId, +// this.contant, +// this.contantNo, +// this.currentUserId, +// this.currentEmployeeNumber, +// this.currentUserName, +// this.currentUserEmail, +// this.currentFullName, +// this.targetUserId, +// this.targetEmployeeNumber, +// this.targetUserName, +// this.targetUserEmail, +// this.targetFullName, +// this.encryptedTargetUserId, +// this.encryptedTargetUserName, +// this.chatEventId, +// this.fileTypeId, +// this.isSeen, +// this.isDelivered, +// this.createdDate, +// this.chatSource, +// this.conversationId, +// this.fileTypeResponse, +// this.userChatReplyResponse, +// this.deviceToken, +// this.isHuaweiDevice, +// this.platform, +// this.voipToken}); +// +// ChatHistoryResponse.fromJson(Map json) { +// userChatHistoryId = json['userChatHistoryId']; +// userChatHistoryLineId = json['userChatHistoryLineId']; +// contant = json['contant']; +// contantNo = json['contantNo']; +// currentUserId = json['currentUserId']; +// currentEmployeeNumber = json['currentEmployeeNumber']; +// currentUserName = json['currentUserName']; +// currentUserEmail = json['currentUserEmail']; +// currentFullName = json['currentFullName']; +// targetUserId = json['targetUserId']; +// targetEmployeeNumber = json['targetEmployeeNumber']; +// targetUserName = json['targetUserName']; +// targetUserEmail = json['targetUserEmail']; +// targetFullName = json['targetFullName']; +// encryptedTargetUserId = json['encryptedTargetUserId']; +// encryptedTargetUserName = json['encryptedTargetUserName']; +// chatEventId = json['chatEventId']; +// fileTypeId = json['fileTypeId']; +// isSeen = json['isSeen']; +// isDelivered = json['isDelivered']; +// createdDate = json['createdDate']; +// chatSource = json['chatSource']; +// conversationId = json['conversationId']; +// fileTypeResponse = json['fileTypeResponse'] != null ? new FileTypeResponse.fromJson(json['fileTypeResponse']) : null; +// userChatReplyResponse = json['userChatReplyResponse']; +// deviceToken = json['deviceToken']; +// isHuaweiDevice = json['isHuaweiDevice']; +// platform = json['platform']; +// voipToken = json['voipToken']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['userChatHistoryId'] = this.userChatHistoryId; +// data['userChatHistoryLineId'] = this.userChatHistoryLineId; +// data['contant'] = this.contant; +// data['contantNo'] = this.contantNo; +// data['currentUserId'] = this.currentUserId; +// data['currentEmployeeNumber'] = this.currentEmployeeNumber; +// data['currentUserName'] = this.currentUserName; +// data['currentUserEmail'] = this.currentUserEmail; +// data['currentFullName'] = this.currentFullName; +// data['targetUserId'] = this.targetUserId; +// data['targetEmployeeNumber'] = this.targetEmployeeNumber; +// data['targetUserName'] = this.targetUserName; +// data['targetUserEmail'] = this.targetUserEmail; +// data['targetFullName'] = this.targetFullName; +// data['encryptedTargetUserId'] = this.encryptedTargetUserId; +// data['encryptedTargetUserName'] = this.encryptedTargetUserName; +// data['chatEventId'] = this.chatEventId; +// data['fileTypeId'] = this.fileTypeId; +// data['isSeen'] = this.isSeen; +// data['isDelivered'] = this.isDelivered; +// data['createdDate'] = this.createdDate; +// data['chatSource'] = this.chatSource; +// data['conversationId'] = this.conversationId; +// if (this.fileTypeResponse != null) { +// data['fileTypeResponse'] = this.fileTypeResponse!.toJson(); +// } +// data['userChatReplyResponse'] = this.userChatReplyResponse; +// data['deviceToken'] = this.deviceToken; +// data['isHuaweiDevice'] = this.isHuaweiDevice; +// data['platform'] = this.platform; +// data['voipToken'] = this.voipToken; +// return data; +// } +// } +// +// class FileTypeResponse { +// int? fileTypeId; +// String? fileTypeName; +// String? fileTypeDescription; +// String? fileKind; +// String? fileName; +// +// FileTypeResponse({this.fileTypeId, this.fileTypeName, this.fileTypeDescription, this.fileKind, this.fileName}); +// +// FileTypeResponse.fromJson(Map json) { +// fileTypeId = json['fileTypeId']; +// fileTypeName = json['fileTypeName']; +// fileTypeDescription = json['fileTypeDescription']; +// fileKind = json['fileKind']; +// fileName = json['fileName']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['fileTypeId'] = this.fileTypeId; +// data['fileTypeName'] = this.fileTypeName; +// data['fileTypeDescription'] = this.fileTypeDescription; +// data['fileKind'] = this.fileKind; +// data['fileName'] = this.fileName; +// return data; +// } +// } diff --git a/lib/modules/cx_module/chat/view_all_attachment_page.dart b/lib/modules/cx_module/chat/view_all_attachment_page.dart new file mode 100644 index 00000000..331997ee --- /dev/null +++ b/lib/modules/cx_module/chat/view_all_attachment_page.dart @@ -0,0 +1,309 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_style.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +import 'chat_api_client.dart'; + +class ViewAllAttachmentPage extends StatefulWidget { + int moduleId; + int requestId; + + ViewAllAttachmentPage({required this.moduleId, required this.requestId, Key? key}) : super(key: key); + + @override + _ViewAllAttachmentPageState createState() { + return _ViewAllAttachmentPageState(); + } +} + +class _ViewAllAttachmentPageState extends State { + List? attachments; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + Future> viewAllDocuments(int moduleId, int requestId) async { + try { + attachments ??= await ChatApiClient().viewAllDocuments(moduleId, requestId); + return attachments!; + } catch (ex) {} + return []; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.neutral100, + appBar: const DefaultAppBar(title: "Documents"), + body: FutureBuilder>( + future: viewAllDocuments(widget.moduleId, widget.requestId), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + bool isLoading = false; + if (snapshot.connectionState == ConnectionState.waiting) { + isLoading = true; + } + if (snapshot.hasData && snapshot.data!.isEmpty) { + return NoDataFound(message: context.translation.noDataFound).center; + } + + return ListView.separated( + itemCount: isLoading ? 9 : snapshot.data!.length, + padding: const EdgeInsets.all(16), + separatorBuilder: (cxt, index) => 12.height, + itemBuilder: (BuildContext context, int index) { + if (isLoading) { + return Container(height: 48).toShimmer(context: context, isShow: isLoading); + } + return AttachmentFileViewer(snapshot.data![index]); + }, + ); + }), + ); + } +} + +class AttachmentFileViewer extends StatelessWidget { + ChatAttachment chatAttachment; + + AttachmentFileViewer(this.chatAttachment, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: checkFileInLocalStorage(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + bool isImage = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "png" || + chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpg" || + chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "pdf"; + bool isExcel = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "xlsx"; + bool isAudio = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "m4a" || chatAttachment.storedFileName!.split(".").last.toLowerCase() == "mp3"; + + bool isLoading = snapshot.connectionState == ConnectionState.waiting; + + Widget widget; + if (isLoading) { + if (isAudio) { + return Container( + width: double.infinity, + height: 48, + decoration: ShapeDecoration( + color: AppColor.background(context), + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: (context.isDark ? AppColor.neutral20 : AppColor.neutral30)), + borderRadius: BorderRadius.circular(32), + ), + ), + child: SizedBox(width: 24, height: 24, child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2).center), + ); + } + widget = const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2)); + } else { + if (isImage) { + widget = Image.file(snapshot.data!, width: 48, height: 48, fit: BoxFit.contain); + } else { + widget = Image.asset( + "assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png", + fit: BoxFit.contain, + width: 48, + height: 48); + } + } + if (isAudio) { + if (snapshot.data == null) { + return Text("Failed to load", style: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60)); + } + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASoundPlayer(audio: snapshot.data!.path), + 4.height, + Text( + chatAttachment.createdOn!.toServiceRequestDetailsFormat, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ).paddingOnly(start: 60), + ], + )); + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(8), + child: Row( + children: [ + ClipRRect(borderRadius: BorderRadius.circular(8.0), child: widget), + 12.width, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + chatAttachment.storedFileName!, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 4.height, + Text( + chatAttachment.createdOn!.toServiceRequestDetailsFormat, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + ], + ).expanded + ], + ), + ).onPress(() { + if (isImage) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea( + child: InteractiveViewer(child: Image.file(snapshot.data!)).center, + ), + ), + ), + ); + } else { + OpenFile.open(snapshot.data!.path); + } + }); + return Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: snapshot.connectionState == ConnectionState.waiting + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ).center + : snapshot.hasData + ? Row( + children: [ + getImage(snapshot.data!), + getFile(context, snapshot.data!), + ], + ) + : const Icon(Icons.broken_image_rounded), + ); + + return Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: snapshot.connectionState == ConnectionState.waiting + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ).center + : snapshot.hasData + ? Row( + children: [ + getImage(snapshot.data!), + getFile(context, snapshot.data!), + ], + ) + : const Icon(Icons.broken_image_rounded), + ); + }); + } + + Widget getFile(BuildContext context, File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + return MaterialButton( + padding: EdgeInsets.zero, + onPressed: () async { + if (isImage) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea( + child: InteractiveViewer(child: Image.file(file)).center, + ), + ), + ), + ); + } else { + OpenFile.open(file.path); + } + // else { + // // if (!await launchUrl(Uri.parse(URLs.getFileUrl(file.path)!), mode: LaunchMode.externalApplication)) { + // // Fluttertoast.showToast(msg: "UnExpected Error with file."); + // // throw Exception('Could not launch'); + // // } + // } + }, + ); + } + + Widget getImage(File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + if (isImage) { + return Image.file(file); + } + return Image.asset("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png"); + } + + Future checkFileInLocalStorage() async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = '${tempDir.path}/${chatAttachment.storedFileName}'; + File tempFile = File(tempPath); + bool exists = await tempFile.exists(); + if (exists) { + return tempFile; + } else { + return downloadFile(); + } + } + + Future downloadFile() async { + try { + return await ChatApiClient().downloadFileWithHttp(chatAttachment.downloadUrl!, fileName: chatAttachment.storedFileName!, fileTypeDescription: "", fileSource: 0); + } catch (ex) { + return null; + } + } +} diff --git a/lib/modules/cx_module/survey/questionnaire_model.dart b/lib/modules/cx_module/survey/questionnaire_model.dart new file mode 100644 index 00000000..9f2ac626 --- /dev/null +++ b/lib/modules/cx_module/survey/questionnaire_model.dart @@ -0,0 +1,172 @@ +class Questionnaire { + int? questionnaireId; + int? surveySubmissionId; + SurveyType? surveyType; + String? surveyName; + String? surveyDescription; + ServiceRequestDetails? serviceRequestDetails; + List? surveyQuestions; + + Questionnaire({this.questionnaireId, this.surveySubmissionId, this.surveyType, this.surveyName, this.surveyDescription, this.serviceRequestDetails, this.surveyQuestions}); + + Questionnaire.fromJson(Map json) { + questionnaireId = json['questionnaireId']; + surveySubmissionId = json['surveySubmissionId']; + surveyType = json['surveyType'] != null ? new SurveyType.fromJson(json['surveyType']) : null; + surveyName = json['surveyName']; + surveyDescription = json['surveyDescription']; + serviceRequestDetails = json['serviceRequestDetails'] != null ? new ServiceRequestDetails.fromJson(json['serviceRequestDetails']) : null; + if (json['surveyQuestions'] != null) { + surveyQuestions = []; + json['surveyQuestions'].forEach((v) { + surveyQuestions!.add(new SurveyQuestions.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['questionnaireId'] = this.questionnaireId; + data['surveySubmissionId'] = this.surveySubmissionId; + if (this.surveyType != null) { + data['surveyType'] = this.surveyType!.toJson(); + } + data['surveyName'] = this.surveyName; + data['surveyDescription'] = this.surveyDescription; + if (this.serviceRequestDetails != null) { + data['serviceRequestDetails'] = this.serviceRequestDetails!.toJson(); + } + if (this.surveyQuestions != null) { + data['surveyQuestions'] = this.surveyQuestions!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SurveyType { + int? id; + String? name; + int? value; + + SurveyType({this.id, this.name, this.value}); + + SurveyType.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + data['value'] = this.value; + return data; + } +} + +class ServiceRequestDetails { + int? serviceRequestTypeId; + String? serviceRequestType; + String? serviceRequestNo; + + ServiceRequestDetails({this.serviceRequestTypeId, this.serviceRequestType, this.serviceRequestNo}); + + ServiceRequestDetails.fromJson(Map json) { + serviceRequestTypeId = json['serviceRequestTypeId']; + serviceRequestType = json['serviceRequestType']; + serviceRequestNo = json['serviceRequestNo']; + } + + Map toJson() { + final Map data = new Map(); + data['serviceRequestTypeId'] = this.serviceRequestTypeId; + data['serviceRequestType'] = this.serviceRequestType; + data['serviceRequestNo'] = this.serviceRequestNo; + return data; + } +} + +class SurveyQuestions { + int? questionId; + String? questionText; + SurveyType? questionType; + bool? isMandatory; + List? surveyAnswerOptions; + + SurveyQuestions({this.questionId, this.questionText, this.questionType, this.surveyAnswerOptions, this.isMandatory}); + + SurveyQuestions.fromJson(Map json) { + questionId = json['questionId']; + questionText = json['questionText']; + isMandatory = json['isMandatory']; + questionType = json['questionType'] != null ? new SurveyType.fromJson(json['questionType']) : null; + if (json['surveyAnswerOptions'] != null) { + surveyAnswerOptions = []; + json['surveyAnswerOptions'].forEach((v) { + surveyAnswerOptions!.add(new SurveyAnswerOptions.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['questionId'] = this.questionId; + data['questionText'] = this.questionText; + data['isMandatory'] = this.isMandatory; + if (this.questionType != null) { + data['questionType'] = this.questionType!.toJson(); + } + if (this.surveyAnswerOptions != null) { + data['surveyAnswerOptions'] = this.surveyAnswerOptions!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SurveyAnswerOptions { + int? optionId; + String? optionText; + int? displayOrder; + + SurveyAnswerOptions({this.optionId, this.optionText, this.displayOrder}); + + SurveyAnswerOptions.fromJson(Map json) { + optionId = json['optionId']; + optionText = json['optionText']; + displayOrder = json['displayOrder']; + } + + Map toJson() { + final Map data = new Map(); + data['optionId'] = this.optionId; + data['optionText'] = this.optionText; + data['displayOrder'] = this.displayOrder; + return data; + } +} + +class SurveyAnswers { + int? questionId; + int? surveyAnswerOptionId; + String? surveyAnswerText; + int? surveyAnswerRating; + + SurveyAnswers({this.questionId, this.surveyAnswerOptionId, this.surveyAnswerText, this.surveyAnswerRating}); + + SurveyAnswers.fromJson(Map json) { + questionId = json['questionId']; + surveyAnswerOptionId = json['surveyAnswerOptionId']; + surveyAnswerText = json['surveyAnswerText']; + surveyAnswerRating = json['surveyAnswerRating']; + } + + Map toJson() { + final Map data = new Map(); + data['questionId'] = this.questionId; + data['surveyAnswerOptionId'] = this.surveyAnswerOptionId; + data['surveyAnswerText'] = this.surveyAnswerText; + data['surveyAnswerRating'] = this.surveyAnswerRating; + return data; + } +} diff --git a/lib/modules/cx_module/survey/survey_page.dart b/lib/modules/cx_module/survey/survey_page.dart new file mode 100644 index 00000000..e061050c --- /dev/null +++ b/lib/modules/cx_module/survey/survey_page.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/helper/utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_rooms_page.dart'; +import 'package:test_sa/modules/cx_module/survey/questionnaire_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; + +import 'survey_provider.dart'; + +class SurveyPage extends StatefulWidget { + // int moduleId; + // int requestId; + int surveyId; + + SurveyPage({Key? key, + // required this.moduleId, required this.requestId, + required this.surveyId}) : super(key: key); + + @override + _SurveyPageState createState() { + return _SurveyPageState(); + } +} + +class _SurveyPageState extends State { + // int serviceSatisfiedRating = -1; + int serviceProvidedRating = -1; + String comments = ""; + String message = ""; + + bool loading = false; + + Questionnaire? questionnaire; + + List answers = []; + + @override + void initState() { + super.initState(); + getSurveyQuestion(); + } + + void getSurveyQuestion() async { + loading = true; + setState(() {}); + await Provider.of(context, listen: false).getQuestionnaire(widget.surveyId, (msg, questions) { + message = msg; + questionnaire = questions; + }); + for (int i = 0; i < (questionnaire?.surveyQuestions?.length ?? 0); i++) { + answers.add(SurveyAnswers( + questionId: questionnaire!.surveyQuestions![i].questionId!, + surveyAnswerRating: -1, + )); + } + loading = false; + setState(() {}); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.neutral100, + appBar: const DefaultAppBar(title: "Survey"), + body: loading + ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3).center + : (questionnaire == null) + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + message.isNotEmpty ? message : "Failed to get questionnaire", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w500), + ), + 24.height, + AppFilledButton( + label: message.isNotEmpty ? "Go Back " : "Retry", + maxWidth: true, + buttonColor: AppColor.primary10, + onPressed: () { + message.isNotEmpty ? Navigator.pop(context) : getSurveyQuestion(); + }, + ).paddingOnly(start: 48, end: 48) + ], + ).center + : Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (cxt, index) { + SurveyQuestions question = questionnaire!.surveyQuestions![index]; + return question.questionType?.value == 4 + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + question.questionText ?? "", + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + 12.height, + SizedBox( + height: 32, + child: ListView.separated( + itemBuilder: (cxt, _index) => (answers[index].surveyAnswerRating! >= _index ? 'star_filled'.toSvgAsset() : 'star_empty'.toSvgAsset()).onPress(() { + setState(() { + answers[index].surveyAnswerRating = _index; + // serviceSatisfiedRating = _index; + }); + }), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (cxt, index) => 8.width, + itemCount: 5, + scrollDirection: Axis.horizontal, + ), + ), + ], + ) + : AppTextFormField( + initialValue: answers[index].surveyAnswerText, + textInputType: TextInputType.multiline, + alignLabelWithHint: true, + labelText: questionnaire!.surveyQuestions![index].questionText, + backgroundColor: AppColor.fieldBgColor(context), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff767676)), + floatingLabelStyle: + TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)), + showShadow: false, + onChange: (value) { + answers[index].surveyAnswerText = value; + }, + ); + }, + separatorBuilder: (cxt, index) => 16.height, + itemCount: questionnaire?.surveyQuestions?.length ?? 0) + .toShadowContainer(context, borderRadius: 20, showShadow: false), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: context.translation.submit, + buttonColor: AppColor.primary10, + onPressed: () async { + FocusScope.of(context).unfocus(); + if (validateAnswers()) { + Map payload = { + "surveySubmissionId": questionnaire!.surveySubmissionId, + "questionnaireId": questionnaire!.questionnaireId, + "submittedUserId": context.userProvider.user!.userID, + "surveySubmissionStatusId": 0, + "surveyAnswers": answers.map((element) => element.toJson()).toList(), + }; + Utils.showLoading(context); + bool isSuccess = await Provider.of(context, listen: false).submitQuestionare(payload); + Utils.hideLoading(context); + if (isSuccess) { + context.showConfirmDialog("Thanks for submitting the Feedback. Its value for us to improve our system and overall experience.", title: "Thank you!", onTap: () { + Navigator.pop(context); + Navigator.pop(context); + }); + } //reload Data + } + }, + ), + ), + ], + ), + ); + } + + bool validateAnswers() { + bool status = true; + for (int i = 0; i < answers.length; i++) { + if (questionnaire!.surveyQuestions![i].isMandatory ?? false) { + if (questionnaire!.surveyQuestions![i].questionType!.value == 4) { + if (answers[i].surveyAnswerRating! < 0) { + "Please rate (${questionnaire!.surveyQuestions![i].questionText})".showToast; + status = false; + break; + } + } else if (questionnaire!.surveyQuestions![i].questionType!.value == 3) { + answers[i].surveyAnswerRating = null; + if ((answers[i].surveyAnswerText ?? "").isEmpty) { + "Please answer (${questionnaire!.surveyQuestions![i].questionText})".showToast; + status = false; + break; + } + } + } + } + return status; + } +} diff --git a/lib/modules/cx_module/survey/survey_provider.dart b/lib/modules/cx_module/survey/survey_provider.dart new file mode 100644 index 00000000..cc1b73f6 --- /dev/null +++ b/lib/modules/cx_module/survey/survey_provider.dart @@ -0,0 +1,52 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; + +import 'questionnaire_model.dart'; + +class SurveyProvider with ChangeNotifier { + bool loading = false; + + void reset() { + loading = false; + // questionnaire = null; + // ChatApiClient().chatLoginResponse = null; + } + + getQuestionnaire(int surveySubmissionId, Function(String message, Questionnaire? questionnaire) onResponse) async { + String responseMsg = ""; + Questionnaire? q; + try { + final response = await ApiManager.instance.get("${URLs.getQuestionnaire}?surveySubmissionId=$surveySubmissionId", enableToastMessage: false); + if (response.statusCode >= 200 && response.statusCode < 300) { + q = Questionnaire.fromJson(jsonDecode(response.body)["data"]); + } else { + if (jsonDecode(response.body)["message"] != null && jsonDecode(response.body)["message"].toString().isNotEmpty) { + responseMsg = jsonDecode(response.body)["message"]; + } else {} + } + } catch (ex) { + "Failed, Retry.".showToast; + } + onResponse(responseMsg, q); + + // return null; + } + + Future submitQuestionare(Map payload) async { + try { + final response = await ApiManager.instance.post(URLs.submitSurvey, body: payload); + if (response.statusCode >= 200 && response.statusCode < 300) { + return true; + } + } catch (ex) { + "Failed, Retry.".showToast; + } + return false; + } +} diff --git a/lib/modules/internal_audit_module/models/engineer_data_model.dart b/lib/modules/internal_audit_module/models/engineer_data_model.dart new file mode 100644 index 00000000..dbb34d24 --- /dev/null +++ b/lib/modules/internal_audit_module/models/engineer_data_model.dart @@ -0,0 +1,53 @@ +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class EngineerData { + int? id; + String? debrief; + String? startTime; + String? endTime; + double? totalHours; + bool? isComplete; + int? statusId; + int? requestId; + List? attachments; + + EngineerData({ + this.id, + this.debrief, + this.startTime, + this.endTime, + this.totalHours, + this.isComplete, + this.statusId, + this.requestId, + this.attachments, + }); + + EngineerData.fromJson(Map json) { + id = json['id']; + debrief = json['debrief']; + startTime = json['startTime']; + endTime = json['endTime']; + totalHours = (json['totalHours'] as num?)?.toDouble(); + isComplete = json['isComplete']; + statusId = json['statusId']; + requestId = json['requestId']; + attachments = json['attachments'] != null ? (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList() : []; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['debrief'] = debrief; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['totalHours'] = totalHours; + data['isComplete'] = isComplete; + data['statusId'] = statusId; + data['requestId'] = requestId; + if (attachments != null) { + data['attachments'] = attachments!.map((e) => e.toJson()).toList(); + } + return data; + } +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart b/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart new file mode 100644 index 00000000..92f41bab --- /dev/null +++ b/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart @@ -0,0 +1,300 @@ + import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; + +class EquipmentInternalAuditDataModel { + int? id; + String? requestNo; + String? createdDate; + int? requestNoSequence; + Auditor? auditor; + Status? status; + Asset? asset; + AssignEmployee? assignEmployee; + dynamic manufacture; + String? remarks; + List? equipmentsFindings; + List? attachments; + EngineerData? engineerData; + + + EquipmentInternalAuditDataModel({ + this.id, + this.requestNo, + this.createdDate, + this.requestNoSequence, + this.auditor, + this.status, + this.asset, + this.assignEmployee, + this.manufacture, + this.remarks, + this.equipmentsFindings, + this.attachments, + this.engineerData, + }); + + EquipmentInternalAuditDataModel.fromJson(Map json) { + id = json['id']; + requestNo = json['requestNo']; + createdDate = json['createdDate']; + requestNoSequence = json['requestNoSequence']; + auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null; + status = json['status'] != null ? Status.fromJson(json['status']) : null; + asset = json['asset'] != null ? Asset.fromJson(json['asset']) : null; + assignEmployee = json['assignEmployee'] != null ? AssignEmployee.fromJson(json['assignEmployee']) : null; + manufacture = json['manufacture']; + remarks = json['remarks']; + if (json['equipmentsFindings'] != null) { + equipmentsFindings = []; + json['equipmentsFindings'].forEach((v) { + equipmentsFindings!.add(EquipmentsFinding.fromJson(v)); + }); + } + attachments = json['attachments'] != null + ? (json['attachments'] as List) + .map((e) => InternalAuditAttachments.fromJson(e)) + .where((e) => e.name != null) // optional filter if you want to skip null names + .toList() + : []; + engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null; + + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['requestNo'] = requestNo; + data['createdDate'] = createdDate; + data['requestNoSequence'] = requestNoSequence; + if (auditor != null) { + data['auditor'] = auditor!.toJson(); + } + if (status != null) { + data['status'] = status!.toJson(); + } + if (asset != null) { + data['asset'] = asset!.toJson(); + } + if (assignEmployee != null) { + data['assignEmployee'] = assignEmployee!.toJson(); + } + data['manufacture'] = manufacture; + data['remarks'] = remarks; + if (equipmentsFindings != null) { + data['equipmentsFindings'] = + equipmentsFindings!.map((v) => v.toJson()).toList(); + } + data['attachments'] = attachments; + if (engineerData != null) data['engineerData'] = engineerData!.toJson(); + return data; + } +} + +class Auditor { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + Auditor({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + Auditor.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + +class Status { + int? id; + String? name; + int? value; + + Status({this.id, this.name, this.value}); + + Status.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['value'] = value; + return data; + } +} + +class Asset { + int? id; + String? assetNumber; + String? assetSerialNo; + String? assetName; + String? modelName; + String? manufacturerName; + String? siteName; + int? siteId; + String? modelDefinition; + + Asset({ + this.id, + this.assetNumber, + this.assetSerialNo, + this.assetName, + this.modelName, + this.manufacturerName, + this.siteName, + this.siteId, + this.modelDefinition, + }); + + Asset.fromJson(Map json) { + id = json['id']; + assetNumber = json['assetNumber']; + assetSerialNo = json['assetSerialNo']; + assetName = json['assetName']; + modelName = json['modelName']; + manufacturerName = json['manufacturerName']; + siteName = json['siteName']; + siteId = json['siteId']; + modelDefinition = json['modelDefinition']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['assetNumber'] = assetNumber; + data['assetSerialNo'] = assetSerialNo; + data['assetName'] = assetName; + data['modelName'] = modelName; + data['manufacturerName'] = manufacturerName; + data['siteName'] = siteName; + data['siteId'] = siteId; + data['modelDefinition'] = modelDefinition; + return data; + } +} + +class AssignEmployee { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + AssignEmployee({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + AssignEmployee.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + +class EquipmentsFinding { + int? id; + Finding? finding; + + EquipmentsFinding({this.id, this.finding}); + + EquipmentsFinding.fromJson(Map json) { + id = json['id']; + finding = json['finding'] != null ? Finding.fromJson(json['finding']) : null; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + if (finding != null) { + data['finding'] = finding!.toJson(); + } + return data; + } +} + +class Finding { + int? id; + String? name; + int? value; + + Finding({this.id, this.name, this.value}); + + Finding.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['value'] = value; + return data; + } +} diff --git a/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart b/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart new file mode 100644 index 00000000..e6a79e91 --- /dev/null +++ b/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart @@ -0,0 +1,41 @@ +import 'package:flutter/src/widgets/framework.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/models/device/asset.dart'; +import 'package:test_sa/models/fault_description.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class EquipmentInternalAuditFormModel { + int? id=0; + String? deviceArName; + String? auditorId; + String? woOrderNo; + List? devicePhotos; + List findings =[]; + List attachments = []; + String? remarks; + Asset? device; + EquipmentInternalAuditFormModel({ + this.id, + this.deviceArName, + this.auditorId, + this.devicePhotos, + this.woOrderNo, + this.remarks, + }); + + Map toJson() { + return { + 'id': id??0, + 'assetId': device?.id, + 'auditorId': auditorId, + 'findings': findings.map((e) => {'findingId': e.value}).toList(), + 'attachments': attachments.map((e) => e.toJson()).toList(), + 'remarks': remarks, + }; + } + + +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart b/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart new file mode 100644 index 00000000..5e21d5ae --- /dev/null +++ b/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart @@ -0,0 +1,27 @@ +import 'dart:developer'; + +class InternalAuditAttachments { + InternalAuditAttachments({this.id, this.originalName, this.name, this.createdBy}); + + int? id; + String? name; + String? originalName; + String? createdBy; + + InternalAuditAttachments.fromJson(Map json) { + log('name is ${json['name']}'); + id = json['id']; + name = (json['name'] != null && !json['name'].toString().startsWith('data:image/jpeg')) ? json['name'] : null; + originalName = json['originalName']; + createdBy = json['createdBy']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['originalName'] = originalName; + // data['createdBy'] = createdBy; + return data; + } +} diff --git a/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart b/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart new file mode 100644 index 00000000..708ab2d1 --- /dev/null +++ b/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart @@ -0,0 +1,35 @@ +class InternalAuditTimerModel { + int? id; + String? startDate; + String? endDate; + double? totalWorkingHour; + String? comment; + + InternalAuditTimerModel({ + this.id, + this.startDate, + this.endDate, + this.totalWorkingHour, + this.comment, + }); + + factory InternalAuditTimerModel.fromJson(Map json) { + return InternalAuditTimerModel( + id: json['id'] as int?, + startDate: json['startDate'], + endDate: json['endDate'], + totalWorkingHour: (json['totalWorkingHours'] as num?)?.toDouble(), + comment: json['comment'] as String?, + ); + } + + Map toJson() { + return { + 'id': id, + 'startDate': startDate, + 'endDate': endDate, + 'totalWorkingHours': totalWorkingHour, + 'comment': comment, + }; + } +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart b/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart new file mode 100644 index 00000000..0546ccdc --- /dev/null +++ b/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart @@ -0,0 +1,138 @@ +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; + +class SystemInternalAuditDataModel { + int? id; + int? assetGroupId; + String? requestNo; + int? requestNoSequence; + Auditor? auditor; + Lookup? status; + Lookup? findingType; + String? findingDescription; + Lookup? workOrderType; + Auditor? assignEmployee; + SystemAuditWorkOrderDetailModel? workOrderDetails; // Already exists as a separate model + String? createdBy; + String? createdDate; + String? modifiedBy; + String? modifiedDate; + List? attachments; + EngineerData? engineerData; + + SystemInternalAuditDataModel({ + this.id, + this.assetGroupId, + this.requestNo, + this.requestNoSequence, + this.auditor, + this.status, + this.findingType, + this.findingDescription, + this.workOrderType, + this.assignEmployee, + this.workOrderDetails, + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate, + this.attachments, + this.engineerData, + }); + + SystemInternalAuditDataModel.fromJson(Map json) { + id = json['id']; + assetGroupId = json['assetGroupId']; + requestNo = json['requestNo']; + requestNoSequence = json['requestNoSequence']; + auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null; + status = json['status'] != null ? Lookup.fromJson(json['status']) : null; + findingType = json['findingType'] != null ? Lookup.fromJson(json['findingType']) : null; + findingDescription = json['findingDescription']; + workOrderType = json['workOrderType'] != null ? Lookup.fromJson(json['workOrderType']) : null; + assignEmployee = json['assignEmployee'] != null ? Auditor.fromJson(json['assignEmployee']) : null; + workOrderDetails = json['workOrderDetails'] != null ? SystemAuditWorkOrderDetailModel.fromJson(json['workOrderDetails']) : null; // Keep as-is + createdBy = json['createdBy']; + createdDate = json['createdDate']; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate']; + attachments = json['attachments'] != null + ? (json['attachments'] as List) + .map((e) => InternalAuditAttachments.fromJson(e)) + .where((e) => e.name != null) // optional filter if you want to skip null names + .toList() + : []; + engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['assetGroupId'] = assetGroupId; + data['requestNo'] = requestNo; + data['requestNoSequence'] = requestNoSequence; + if (auditor != null) data['auditor'] = auditor!.toJson(); + if (status != null) data['status'] = status!.toJson(); + if (findingType != null) data['findingType'] = findingType!.toJson(); + data['findingDescription'] = findingDescription; + if (workOrderType != null) data['workOrderType'] = workOrderType!.toJson(); + if (assignEmployee != null) data['assignEmployee'] = assignEmployee!.toJson(); + data['workOrderDetails'] = workOrderDetails; + data['createdBy'] = createdBy; + data['createdDate'] = createdDate; + data['modifiedBy'] = modifiedBy; + data['modifiedDate'] = modifiedDate; + if (engineerData != null) data['engineerData'] = engineerData!.toJson(); + return data; + } +} + +class Auditor { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + Auditor({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + Auditor.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + + diff --git a/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart b/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart new file mode 100644 index 00000000..3fda34d0 --- /dev/null +++ b/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart @@ -0,0 +1,202 @@ +import 'dart:developer'; + +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class SystemInternalAuditFormModel { + int? id; + String? auditorId; + String? assignEmployeeId; + Lookup? findingType; + String? findingDescription; + Lookup? workOrderType; + int? correctiveMaintenanceId; + int? planPreventiveVisitId; + int? assetTransferId; + List? attachments = []; + int? taskJobId; + int? taskAlertJobId; + int? gasRefillId; + int? planRecurrentTaskId; + int? statusId; + SystemAuditWorkOrderDetailModel? workOrderDetailModel; + + SystemInternalAuditFormModel({ + this.id, + this.auditorId, + this.assignEmployeeId, + this.findingType, + this.findingDescription, + this.workOrderType, + this.correctiveMaintenanceId, + this.planPreventiveVisitId, + this.assetTransferId, + this.taskJobId, + this.taskAlertJobId, + this.gasRefillId, + this.planRecurrentTaskId, + this.workOrderDetailModel, + this.statusId, + this.attachments, + }); + + Map toJson() { + return { + // 'id': id, + 'auditorId': auditorId, + 'assignEmployeeId': assignEmployeeId, + 'findingTypeId': findingType?.id, + 'findingDescription': findingDescription, + 'workOrderTypeId': workOrderType?.id, + 'correctiveMaintenanceId': correctiveMaintenanceId, + 'planPreventiveVisitId': planPreventiveVisitId, + 'assetTransferId': assetTransferId, + 'taskJobId': taskJobId, + 'taskAlertJobId': taskAlertJobId, + 'gasRefillId': gasRefillId, + 'planRecurrentTaskId': planRecurrentTaskId, + 'statusId': statusId, + 'attachments': attachments?.map((e) => e.toJson()).toList(), + }; + } +} + +class WoAutoCompleteModel { + int? id; + String? workOrderNo; + String? displayName; + + WoAutoCompleteModel({this.id, this.workOrderNo, this.displayName}); + + WoAutoCompleteModel.fromJson(Map json) { + id = json['id']; + workOrderNo = json['workOrderNo']; + displayName = json['workOrderNo']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['woOrderNo'] = workOrderNo; + data['displayName'] = workOrderNo; + return data; + } +} + +class SystemAuditWorkOrderDetailModel { + String? createdBy; + DateTime? createdDate; + String? modifiedBy; + DateTime? modifiedDate; + int? id; + String? workOrderNo; + String? woAssignedEngineer; + String? woAssetNo; + String? wosn; + String? woAssetName; + String? woModel; + String? woManufacturer; + String? woSite; + String? woDepartment; + String? wOPMScheduleDate; + String? wOPMActualVisitDate; + String? wOAssetTransferDestinationSite; + String? wOAssetTransferSenderEngineer; + String? wOAssetTransferReceiverEngineer; + String? wOTaskAlertJobImpactedStatus; + String? wOTaskAlertJobAcknowledgment; + String? wOGasRefillType; + String? wORecurrentTaskTaskType; + String? wORecurrentTaskPlanNo; + String? wORecurrentTaskPlanName; + String? wORecurrentTaskStatus; + + SystemAuditWorkOrderDetailModel({ + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate, + this.id, + this.workOrderNo, + this.woAssignedEngineer, + this.woAssetNo, + this.wosn, + this.woAssetName, + this.woModel, + this.woManufacturer, + this.woSite, + this.woDepartment, + this.wOPMScheduleDate, + this.wOPMActualVisitDate, + this.wOAssetTransferDestinationSite, + this.wOAssetTransferSenderEngineer, + this.wOAssetTransferReceiverEngineer, + this.wOTaskAlertJobImpactedStatus, + this.wOTaskAlertJobAcknowledgment, + this.wOGasRefillType, + this.wORecurrentTaskTaskType, + this.wORecurrentTaskPlanNo, + this.wORecurrentTaskPlanName, + this.wORecurrentTaskStatus, + }); + + SystemAuditWorkOrderDetailModel.fromJson(Map json) { + createdBy = json['createdBy']; + createdDate = json['createdDate'] != null ? DateTime.tryParse(json['createdDate']) : null; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate'] != null ? DateTime.tryParse(json['modifiedDate']) : null; + id = json['id']; + workOrderNo = json['workOrderNo']; + woAssignedEngineer = json['woAssignedEngineer']; + woAssetNo = json['woAssetNo']; + wosn = json['wosn']; + woAssetName = json['woAssetName']; + woModel = json['woModel']; + woManufacturer = json['woManufacturer']; + woSite = json['woSite']; + woDepartment = json['woDepartment']; + wOPMScheduleDate = json['wO_PM_ScheduleDate']; + wOPMActualVisitDate = json['wO_PM_ActualVisitDate']; + wOAssetTransferDestinationSite = json['wO_AssetTransfer_DestinationSite']; + wOAssetTransferSenderEngineer = json['wO_AssetTransfer_SenderEngineer']; + wOAssetTransferReceiverEngineer = json['wO_AssetTransfer_ReceiverEngineer']; + wOTaskAlertJobImpactedStatus = json['wO_TaskAlertJob_ImpactedStatus']; + wOTaskAlertJobAcknowledgment = json['wO_TaskAlertJob_Acknowledgment']; + wOGasRefillType = json['wO_GasRefill_Type']; + wORecurrentTaskTaskType = json['wO_RecurrentTask_TaskType']; + wORecurrentTaskPlanNo = json['wO_RecurrentTask_PlanNo']; + wORecurrentTaskPlanName = json['wO_RecurrentTask_PlanName']; + wORecurrentTaskStatus = json['wO_RecurrentTask_Status']; + } + + Map toJson() { + return { + 'createdBy': createdBy, + 'createdDate': createdDate?.toIso8601String(), + 'modifiedBy': modifiedBy, + 'modifiedDate': modifiedDate?.toIso8601String(), + 'id': id, + 'workOrderNo': workOrderNo, + 'woAssignedEngineer': woAssignedEngineer, + 'woAssetNo': woAssetNo, + 'wosn': wosn, + 'woAssetName': woAssetName, + 'woModel': woModel, + 'woManufacturer': woManufacturer, + 'woSite': woSite, + 'woDepartment': woDepartment, + 'wO_PM_ScheduleDate': wOPMScheduleDate, + 'wO_PM_ActualVisitDate': wOPMActualVisitDate, + 'wO_AssetTransfer_DestinationSite': wOAssetTransferDestinationSite, + 'wO_AssetTransfer_SenderEngineer': wOAssetTransferSenderEngineer, + 'wO_AssetTransfer_ReceiverEngineer': wOAssetTransferReceiverEngineer, + 'wO_TaskAlertJob_ImpactedStatus': wOTaskAlertJobImpactedStatus, + 'wO_TaskAlertJob_Acknowledgment': wOTaskAlertJobAcknowledgment, + 'wO_GasRefill_Type': wOGasRefillType, + 'wO_RecurrentTask_TaskType': wORecurrentTaskTaskType, + 'wO_RecurrentTask_PlanNo': wORecurrentTaskPlanNo, + 'wO_RecurrentTask_PlanName': wORecurrentTaskPlanName, + 'wO_RecurrentTask_Status': wORecurrentTaskStatus, + }; + } +} diff --git a/lib/modules/internal_audit_module/models/update_audit_form_model.dart b/lib/modules/internal_audit_module/models/update_audit_form_model.dart new file mode 100644 index 00000000..6942d51f --- /dev/null +++ b/lib/modules/internal_audit_module/models/update_audit_form_model.dart @@ -0,0 +1,61 @@ +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; + +class AuditFormModel { + int? id; + String? createdDate; + int? requestId; + String? debrief; + DateTime? startTime; + DateTime? endTime; + double? totalHours; + List? attachments; + bool? isComplete; + TimerModel? auditTimerModel = TimerModel(); + List? auditTimers = []; + TimerModel? auditTimePicker; + List? timerModelList = []; + + AuditFormModel( + {this.id, + this.requestId, + this.debrief, + this.startTime, + this.endTime, + this.totalHours, + this.createdDate, + this.attachments, + this.isComplete, + this.auditTimerModel, + this.timerModelList, + this.auditTimePicker, + this.auditTimers}); + + AuditFormModel.fromJson(Map json) { + id = json['id']; + requestId = json['requestId']; + debrief = json['debrief']; + startTime = json['startTime'] != null ? DateTime.tryParse(json['startTime']) : null; + endTime = json['endTime'] != null ? DateTime.tryParse(json['endTime']) : null; + totalHours = json['totalHours']?.toDouble(); + if (json['attachments'] != null) { + attachments = (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList(); + } + isComplete = json['isComplete']; + } + + Map toJson() { + return { + 'id': id, + 'requestId': requestId, + 'debrief': debrief, + 'startTime': startTime?.toIso8601String(), + 'endTime': endTime?.toIso8601String(), + // 'auditTimer': auditTimers, + 'totalHours': totalHours, + 'attachments': attachments?.map((e) => e.toJson()).toList(), + 'isComplete': isComplete, + }; + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart new file mode 100644 index 00000000..532a5b14 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart @@ -0,0 +1,147 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart'; +import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; + +class CreateEquipmentInternalAuditForm extends StatefulWidget { + static const String id = "/create-equipment-internal-audit-form"; + + const CreateEquipmentInternalAuditForm({Key? key}) : super(key: key); + + @override + _CreateEquipmentInternalAuditFormState createState() => _CreateEquipmentInternalAuditFormState(); +} + +class _CreateEquipmentInternalAuditFormState extends State with TickerProviderStateMixin { + late TextEditingController _commentController; + final EquipmentInternalAuditFormModel _equipmentinternalAuditModel = EquipmentInternalAuditFormModel(); + final List _attachments = []; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + FocusNode auditCheckListNode = FocusNode(); + + @override + void initState() { + super.initState(); + _commentController = TextEditingController(); + } + + @override + void dispose() { + _commentController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: DefaultAppBar(title: 'Equipment Internal Audit Checklist'.addTranslation), + body: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AssetPicker( + device: _equipmentinternalAuditModel.device, + borderColor: AppColor.black20, + buttonColor: AppColor.white936, + onPick: (asset) async { + _equipmentinternalAuditModel.device = asset; + setState(() {}); + }, + ), + 16.height, + //TODO need to check layout issue... + MultipleItemDropDownMenu( + context: context, + showAsBottomSheet: true, + backgroundColor: AppColor.neutral100, + showShadow: false, + showCancel: true, + requestById: context.userProvider.user?.clientId, + title: 'Internal Audit Checklist'.addTranslation, + initialValue: _equipmentinternalAuditModel.findings, + onSelect: (value) { + _equipmentinternalAuditModel.findings = value ?? []; + }, + ), + 16.height, + AppTextFormField( + backgroundColor: AppColor.fieldBgColor(context), + labelText: 'Remarks'.addTranslation, + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + showShadow: false, + onSaved: (value) { + _equipmentinternalAuditModel.remarks = value; + }, + ), + 16.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + onChange: (value) {}, + buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10), + ), + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton(label: context.translation.submitRequest, buttonColor: AppColor.primary10, onPressed: _submit), + ), + ], + ), + ), + ); + } + + Future _submit() async { + InternalAuditProvider internalAuditProvider = Provider.of(context, listen: false); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _equipmentinternalAuditModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + _equipmentinternalAuditModel.attachments.add(InternalAuditAttachments(id: item.id, name: fileName)); + } + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + _equipmentinternalAuditModel.auditorId = context.userProvider.user?.userID; + bool status = await internalAuditProvider.addEquipmentInternalAudit(context: context, request: _equipmentinternalAuditModel); + Navigator.pop(context); + if(status){ + Navigator.pop(context); + } + } + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart new file mode 100644 index 00000000..9d18f9fd --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart @@ -0,0 +1,207 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; + +class EquipmentInternalAuditDetailPage extends StatefulWidget { + static const String id = "/details-internal-audit"; + + final int auditId; + + const EquipmentInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key); + + @override + _EquipmentInternalAuditDetailPageState createState() { + return _EquipmentInternalAuditDetailPageState(); + } +} + +class _EquipmentInternalAuditDetailPageState extends State { + bool isWoType = true; + EquipmentInternalAuditDataModel? model; + late InternalAuditProvider _internalAuditProvider; + List allAttachments = []; + + @override + void initState() { + super.initState(); + _internalAuditProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((_) { + getAuditData(); + }); + } + + Future getAuditData() async { + model = await _internalAuditProvider.getEquipmentInternalAuditById(widget.auditId); + allAttachments.clear(); + allAttachments = [ + ...(model?.attachments ?? []), + ...(model?.engineerData?.attachments ?? []), + ]; + + + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + //TODO need to check when implementing provider needed or not . + return Scaffold( + appBar: const DefaultAppBar(title: "Request Details"), + body: Selector( + selector: (_, provider) => provider.isLoading, + builder: (_, isLoading, __) { + if (isLoading) return const ALoading(); + if (model == null) return NoDataFound(message: context.translation.noDataFound).center; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + assetDetails(), + 8.height, + requestDetails(), + 8.height, + if (model?.remarks?.isNotEmpty ?? false) ...[ + const Divider().defaultStyle(context), + model!.remarks!.bodyText(context), + 8.height, + ], + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 8.height, + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), + ], + ], + ).paddingAll(0).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Update", + onPressed: () async { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => UpdateEquipmentInternalAuditPage(model: model), + ), + ); + if (result == true) { + await getAuditData(); + setState(() {}); // refresh UI with new model + } + // Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdateEquipmentInternalAuditPage(model: model))); + }), + ), + ], + ); + }, + )); + } + + Widget workOrderInformation() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "WO Info", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + '${context.translation.woNumber}: ${'-'}'.bodyText(context), + '${'WO Type'.addTranslation}: ${'-'}'.bodyText(context), + '${context.translation.site}: ${'-'}'.bodyText(context), + '${context.translation.assetName}: ${'-'}'.bodyText(context), + '${context.translation.manufacture}: ${'-'}'.bodyText(context), + '${context.translation.model}: ${'-'}'.bodyText(context), + ], + ); + } + + Widget requestDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "Request Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + if (model != null && model!.equipmentsFindings != null && model!.equipmentsFindings!.isNotEmpty) + ...model!.equipmentsFindings!.map((item) { + final findingName = item.finding?.name ?? 'Unknown'; + return checklistWidget(value: findingName.addTranslation); + }).toList() + else + Text('No findings available'.addTranslation), + ], + ); + } + + Widget assetDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Asset Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + '${context.translation.assetName}: ${model?.asset?.assetName ?? '-'}'.bodyText(context), + '${context.translation.assetNo}: ${model?.asset?.assetNumber ?? '-'}'.bodyText(context), + '${context.translation.manufacture}: ${model?.asset?.manufacturerName ?? '-'}'.bodyText(context), + '${context.translation.model}: ${model?.asset?.modelName ?? '-'}'.bodyText(context), + ], + ); + } + + Widget checklistWidget({required String value}) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: true, + activeColor: AppColor.neutral120, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity(horizontal: -4, vertical: -3), + onChanged: (value) {}, + ), + value.bodyText(context), + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart new file mode 100644 index 00000000..a17407a2 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart @@ -0,0 +1,115 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class EquipmentInternalAuditItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const EquipmentInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + //TODO need to refactor this code repetation @waseem + if (requestData != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestData!.statusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!), + ), + 1.width.expanded, + Text( + requestData!.transactionDate?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestData?.typeTransaction ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNumber, value: requestData?.assetNumber, context: context), + + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestDetails!.status!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!), + ), + 1.width.expanded, + Text( + requestDetails!.date?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context), + 8.height, + infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context), + infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context), + infoWidget(label: context.translation.model, value: requestDetails!.model, context: context), + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + + Widget infoWidget({required String label, String? value, required BuildContext context}) { + if (value != null && value.isNotEmpty) { + return '$label: $value'.bodyText(context); + } + return const SizedBox(); + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart new file mode 100644 index 00000000..f69e4a10 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart @@ -0,0 +1,322 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/working_time_tile.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; + +class UpdateEquipmentInternalAuditPage extends StatefulWidget { + static const String id = "update-equipment-internal-audit"; + EquipmentInternalAuditDataModel? model; + + UpdateEquipmentInternalAuditPage({Key? key, this.model}) : super(key: key); + + @override + State createState() => _UpdateEquipmentInternalAuditPageState(); +} + +class _UpdateEquipmentInternalAuditPageState extends State { + final bool _isLoading = false; + double totalWorkingHours = 0.0; + AuditFormModel formModel = AuditFormModel(); + final TextEditingController _workingHoursController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + List _attachments = []; + + //TODO need to check if it's needed or not.. + List timerList = []; + + @override + void initState() { + populateForm(); + super.initState(); + } + + void populateForm() { + formModel.requestId = widget.model?.id; + formModel.id = widget.model?.id; + _attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? []; + + calculateWorkingTime(); + } + + void calculateWorkingTime() { + final helperModel = formModel; + final timers = helperModel.auditTimers ?? []; + totalWorkingHours = timers.fold(0.0, (sum, item) { + if (item.startDate == null || item.endDate == null) return sum; + try { + final start = DateTime.parse(item.startDate!); + final end = DateTime.parse(item.endDate!); + final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours + return sum + diffInHours; + } catch (_) { + return sum; + } + }); + timerList = timers.map((e) { + return TimerHistoryModel( + id: e.id, + startTime: e.startDate, + endTime: e.endDate, + workingHours: e.startDate, + ); + }).toList(); + } + + _onSubmit(BuildContext context) async { + bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + InternalAuditProvider provider = Provider.of(context, listen: false); + + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + formModel.auditTimers ??= []; + if (formModel.auditTimePicker != null) { + int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null + endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + } + formModel.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: timer.startAt!.toIso8601String(), // Handle potential null + endDate: timer.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + }); + + _formKey.currentState!.save(); + formModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + formModel.attachments!.add( + InternalAuditAttachments( + id: item.id, + originalName: fileName, + name: fileName, + ), + ); + } + final success = await provider.updateEquipmentInternalAudit(model: formModel); + Navigator.pop(context); + if (formModel.isComplete == true) { + //Navigate to List screen. + } else if (formModel.isComplete == false) { + //Navigate to Detail screen. + if (success) { + Navigator.of(context).pop(true); + } else { + Fluttertoast.showToast(msg: 'Failed to update'); + } + + } + } + + @override + void dispose() { + _workingHoursController.dispose(); + super.dispose(); + } + + void updateTimer({TimerModel? timer}) { + if (timer?.startAt != null && timer?.endAt != null) { + final start = timer!.startAt!; + final end = timer.endAt!; + + final difference = end.difference(start); + final totalHours = difference.inSeconds / 3600.0; + formModel.startTime = start; + formModel.endTime = end; + formModel.totalHours = totalHours; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'Update Information'.addTranslation, + onWillPopScope: () { + formModel.isComplete = false; + _onSubmit(context); + }, + ), + key: _scaffoldKey, + body: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 8.height, + AppTextFormField( + labelText: 'Debrief'.addTranslation, + textInputType: TextInputType.multiline, + hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + alignLabelWithHint: true, + initialValue: formModel.debrief, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + onSaved: (value) { + formModel.debrief = value; + }, + ), + 8.height, + _timerWidget(context, totalWorkingHours), + 16.height, + AttachmentPicker( + label: 'Upload Attachment', + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset( + color: AppColor.primary10, + ), + ), + 8.height, + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60, + textColor: context.isDark ? AppColor.white10 : AppColor.black10, + onPressed: () { + formModel.isComplete = false; + _onSubmit(context); + }).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: () { + formModel.isComplete = true; + _onSubmit(context); + }).expanded, + ], + ), + ), + ], + )), + ), + ).handlePopScope( + cxt: context, + onSave: () { + formModel.isComplete = false; + _onSubmit(context); + }); + } + + Widget _timerWidget(BuildContext context, double totalWorkingHours) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + //TODO multiple timer .. + // AppTimer( + // label: context.translation.timer, + // timer: formModel.auditTimerModel, + // // enabled: enableTimer, + // pickerTimer: formModel.auditTimePicker, + // pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + // onPick: (time) { + // formModel.auditTimePicker = time; + // setState(() {}); + // log('Time picker start ${formModel.auditTimePicker?.startAt}'); + // log('Time picker end ${formModel.auditTimePicker?.endAt}'); + // }, + // timerProgress: (isRunning) {}, + // onChange: (timer) async { + // formModel.auditTimerModel = timer; + // log('start ${formModel.auditTimerModel?.startAt}'); + // log('end ${formModel.auditTimerModel?.endAt}'); + // if (timer.startAt != null && timer.endAt != null) { + // formModel.timerModelList = formModel.timerModelList ?? []; + // formModel.timerModelList!.add(timer); + // } + // setState(() {}); + // log('list length ${formModel.timerModelList?.length}'); + // return true; + // }, + // ), + AppTimer( + label: context.translation.workingHours, + timer: formModel.auditTimerModel, + pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + pickerTimer: formModel.auditTimePicker, + showPicker: false, + onPick: (timer) { + updateTimer(timer: timer); + }, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + ), + timerProgress: (isRunning) {}, + onChange: (timer) async { + updateTimer(timer: timer); + log('here onChange ${timer.startAt}'); + + return true; + }, + ), + if (totalWorkingHours > 0.0) ...[ + 12.height, + WorkingTimeTile( + timerList: timerList, + totalWorkingTime: totalWorkingHours, + ), + ], + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart new file mode 100644 index 00000000..20ab2451 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart @@ -0,0 +1,278 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/device/asset.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; +import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/parts/auto_complete_parts_field.dart'; +import '../../../../../../../new_views/common_widgets/default_app_bar.dart'; + +class CreateSystemInternalAuditForm extends StatefulWidget { + static const String id = "/create-system-audit-view"; + + const CreateSystemInternalAuditForm({Key? key}) : super(key: key); + + @override + _CreateSystemInternalAuditFormState createState() => _CreateSystemInternalAuditFormState(); +} +//TODO remove unnecessary code + +class _CreateSystemInternalAuditFormState extends State with TickerProviderStateMixin { + late TextEditingController _commentController; + late InternalAuditProvider _internalAuditProvider; + final SystemInternalAuditFormModel _model = SystemInternalAuditFormModel(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + final List _attachments = []; + late TextEditingController _woAutoCompleteController; + bool showLoading = false; + + @override + void initState() { + super.initState(); + _commentController = TextEditingController(); + _woAutoCompleteController = TextEditingController(); + _internalAuditProvider = Provider.of(context, listen: false); + } + + @override + void dispose() { + _commentController.dispose(); + _woAutoCompleteController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: DefaultAppBar(title: 'System Internal Audit Checklist'.addTranslation), + body: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: 'Wo Type'.addTranslation, + showShadow: false, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + initialValue: _model.workOrderType, + onSelect: (status) { + if (status != null) { + _model.workOrderType = status; + _model.workOrderDetailModel = SystemAuditWorkOrderDetailModel(); + _clearWorkOrderSelectionIds(); + _woAutoCompleteController.clear(); + setState(() {}); + } + }, + ), + 12.height, + SystemAuditWoAutoCompleteField( + woTypeId: _model.workOrderType?.value, + controller: _woAutoCompleteController, + clearAfterPick: false, + initialValue: '', + onPick: (wo) async { + InternalAuditProvider provider = Provider.of(context, listen: false); + setState(() { + showLoading = true; + }); + _model.workOrderDetailModel = await provider.loadAllWorkOrderDetailsByID(workOrderId: wo.id!, workOrderTypeId: _model.workOrderType!.value!); + updateWorkOrderReference(woTypeId: _model.workOrderType?.value, selectedId: wo.id); + + setState(() { + showLoading = false; + }); + }, + ), + 12.height, + // if(_internalAuditModel.woOrderNo!=null) + _workOrderInfoWidget(context), + 12.height, + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: 'Finding Type'.addTranslation, + showShadow: false, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + initialValue: _model.findingType, + onSelect: (status) { + if (status != null) { + _model.findingType = status; + setState(() {}); + } + }, + ), + 12.height, + AppTextFormField( + backgroundColor: AppColor.fieldBgColor(context), + labelText: 'Finding Description'.addTranslation, + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + showShadow: false, + onSaved: (value) { + _model.findingDescription = value; + }, + ), + 16.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10), + ), + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: context.translation.submitRequest, + onPressed: _submit, + ), + ), + ], + ), + ), + ); + } + + Future _submit() async { + InternalAuditProvider internalAuditProvider = Provider.of(context, listen: false); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _model.attachments=[]; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + _model.attachments?.add(InternalAuditAttachments(id: item.id, name: fileName)); + } + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + _model.auditorId = context.userProvider.user?.userID; + bool status = await internalAuditProvider.addSystemInternalAudit(context: context, request: _model); + Navigator.pop(context); + if (status) { + Navigator.pop(context); + } + } + } + + Widget _workOrderInfoWidget(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: showLoading ? Colors.white : const Color(0xffF4F6FC), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xff212936).withOpacity(.03), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + _model.workOrderType?.name ?? 'WO Type', + style: TextStyle( + fontSize: 14.toScreenWidth, + fontWeight: FontWeight.w500, + fontStyle: FontStyle.normal, + color: Colors.black87, + decoration: TextDecoration.none, + ), + ).expanded, + const Icon( + Icons.info, + color: Color(0xff7D859A), + size: 20, + ), + ], + ), + 6.height, + "${context.translation.site}: ${_model.workOrderDetailModel?.woSite ?? '-'}".bodyText(context), + "${'Eng. Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssignedEngineer ?? '-'}".bodyText(context), + "${'Asset Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssetName ?? '-'}".bodyText(context), + "${context.translation.model}: ${_model.workOrderDetailModel?.woModel ?? '-'}".bodyText(context), + "${context.translation.manufacture}: ${_model.workOrderDetailModel?.woManufacturer ?? '-'}".bodyText(context), + "${'SN'.addTranslation}: ${_model.workOrderDetailModel?.wosn ?? '-'}".bodyText(context), + "${context.translation.assetNo}: ${_model.workOrderDetailModel?.woAssetNo ?? '-'}".bodyText(context), + ], + ), + ).toShimmer(isShow: showLoading, context: context); + } + + void updateWorkOrderReference({int? woTypeId, int? selectedId}) { + _clearWorkOrderSelectionIds(); + if (woTypeId == null || selectedId == null) return; + switch (woTypeId) { + case 1: // CM + _model.correctiveMaintenanceId = selectedId; + break; + case 2: // Gas Refill + _model.gasRefillId = selectedId; + break; + case 3: // Asset Transfer + _model.assetTransferId = selectedId; + break; + case 4: // PPM Request + _model.planPreventiveVisitId = selectedId; + break; + case 5: // Recurrent WOx + _model.planRecurrentTaskId = selectedId; + break; + case 6: // Task + _model.taskJobId = selectedId; + break; + case 7: // Recall & Alert + _model.taskAlertJobId = selectedId; + break; + } + } + + void _clearWorkOrderSelectionIds() { + _model.correctiveMaintenanceId = null; + _model.planPreventiveVisitId = null; + _model.assetTransferId = null; + _model.taskJobId = null; + _model.taskAlertJobId = null; + _model.gasRefillId = null; + _model.planRecurrentTaskId = null; + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart new file mode 100644 index 00000000..9aa79453 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart @@ -0,0 +1,149 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_style.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +class SystemAuditWoAutoCompleteField extends StatefulWidget { + final String initialValue; + final int? woTypeId; + final TextEditingController controller; + final bool clearAfterPick; + final Function(WoAutoCompleteModel) onPick; + + const SystemAuditWoAutoCompleteField({Key? key, required this.initialValue, this.woTypeId, required this.onPick, this.clearAfterPick = true, required this.controller}) : super(key: key); + + @override + _SystemAuditWoAutoCompleteFieldState createState() => _SystemAuditWoAutoCompleteFieldState(); +} + +class _SystemAuditWoAutoCompleteFieldState extends State { + late InternalAuditProvider _provider; + + // + // late TextEditingController _controller; + + bool loading = false; + + @override + void initState() { + // w controller = widget.controller ?? TextEditingController(text: widget.initialValue); + super.initState(); + _provider = Provider.of(context, listen: false); + } + + @override + void didUpdateWidget(covariant SystemAuditWoAutoCompleteField oldWidget) { + // if (widget.initialValue != oldWidget.initialValue) { + // // _controller = widget.controller ?? TextEditingController(text: widget.initialValue); + // } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + if (widget.controller == null) { + // _controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + return Container( + decoration: BoxDecoration( + color: AppColor.background(context), + borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), + // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], + ), + child: Autocomplete( + optionsBuilder: (TextEditingValue textEditingValue) async { + if (textEditingValue.text.isEmpty) { + if (loading) { + setState(() { + loading = false; + }); + } + return const Iterable.empty(); + } + if (!loading) { + setState(() { + loading = true; + }); + } + List workOrders = (await _provider.getWorkOrderByWoType(text: textEditingValue.text, woId:widget.woTypeId)); + setState(() { + loading = false; + }); + return workOrders; + }, + displayStringForOption: (WoAutoCompleteModel option) => option.displayName ?? '', + fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { + return TextField( + controller: widget.controller, + focusNode: fieldFocusNode, + style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + textAlign: TextAlign.start, + decoration: InputDecoration( + border: border, + disabledBorder: border, + focusedBorder: border, + enabledBorder: border, + errorBorder: border, + contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + constraints: const BoxConstraints(), + suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8), + filled: true, + fillColor: AppColor.fieldBgColor(context), + errorStyle: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60), + floatingLabelStyle: AppTextStyle.body1.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? null : AppColor.neutral20), + labelText: context.translation.woNumber, + labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), + suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null, + ), + textInputAction: TextInputAction.search, + onChanged: (text) { + fieldTextEditingController.text = text; + }, + onSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + }, + onSelected: (WoAutoCompleteModel selection) { + if (widget.clearAfterPick) { + widget.controller.clear(); + } else { + widget.controller.text = (selection.displayName ?? ""); + } + widget.onPick(selection); + }, + ), + ); + } + + // String getUrl(int? woTypeId) { + // if (woTypeId == null) return ''; + // String base = URLs.woAutoCompleteBase; + // var urlMap = { + // 1: '$base/ServiceRequest/WorkOrderAutoComplete', // CM + // 2: '$base/GasRefill/GetGasRefillAutoComplete', // Gas Refill + // 3: '$base/AssetTransfer/GetAssetTransferAutoComplete', // Asset Transfer + // 4: '$base/PlanPreventiveVisit/GetAutoCompletePlanPreventiveVisit', // PPM Request + // 5: '$base/PlanRecurrentTasks/GetPlanRecurrentTask', // Recurrent WO + // 6: '$base/TaskJobs/AutocompleteTaskJob', // Task + // 7: '$base/TaskJobs/AutocompleteTaskJob', // Recall & Alert + // }; + // return urlMap[woTypeId] ?? ''; + // } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart new file mode 100644 index 00000000..484d5b4e --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart @@ -0,0 +1,219 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; + +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; + +class SystemInternalAuditDetailPage extends StatefulWidget { + static const String id = "/details-system-internal-audit"; + final int auditId; + + SystemInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key); + + @override + _SystemInternalAuditDetailPageState createState() { + return _SystemInternalAuditDetailPageState(); + } +} + +class _SystemInternalAuditDetailPageState extends State { + bool isWoType = true; + SystemInternalAuditDataModel? model; + late InternalAuditProvider _internalAuditProvider; + List allAttachments = []; + + @override + void initState() { + super.initState(); + _internalAuditProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((_) { + getAuditData(); + }); + } + + Future getAuditData() async { + model = await _internalAuditProvider.getInternalSystemAuditById(widget.auditId); + allAttachments.clear(); + allAttachments = [ + ...(model?.attachments ?? []), + ...(model?.engineerData?.attachments ?? []), + ]; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + //TODO need to check when implementing provider needed or not . + return Scaffold( + appBar: const DefaultAppBar(title: "Request Details"), + body: Selector( + selector: (_, provider) => provider.isLoading, + builder: (_, isLoading, __) { + if (isLoading) return const ALoading(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + assetInformation(), + 8.height, + workOrderInformation(), + 8.height, + //TODO need to check for comments + if (model?.findingDescription?.isNotEmpty ?? false) ...[ + const Divider().defaultStyle(context), + // Text( + // "Comments".addTranslation, + // style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + // ), + model!.findingDescription!.bodyText(context), + ], + //TODO need to check for attachments + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 8.height, + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), + ], + ], + ).paddingAll(0).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Update", + onPressed: () async { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => UpdateSystemInternalAuditPage(model: model), + ), + ); + if (result == true) { + await getAuditData(); + setState(() {}); // refresh UI with new model + } + }), + ), + ], + ); + }, + )); + } + + Widget workOrderInformation() { + final details = model?.workOrderDetails; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "WO Info", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + labelValueText(context, context.translation.woNumber, details?.workOrderNo), + labelValueText(context, 'WO Type'.addTranslation, model?.workOrderType?.name), + labelValueText(context, context.translation.site, details?.woSite), + labelValueText(context, context.translation.assetName, details?.woAssetName), + labelValueText(context, context.translation.manufacture, details?.woManufacturer), + labelValueText(context, context.translation.model, details?.woModel), + ], + ); + } + + Widget requestDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "Request Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + checklistWidget(value: 'Asset Tag'.addTranslation), + checklistWidget(value: 'Expired PM Tag'.addTranslation), + ], + ); + } + + Widget assetInformation() { + final details = model?.workOrderDetails; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Asset Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + labelValueText(context, context.translation.assetName, details?.woAssetName), + labelValueText(context, context.translation.assetNo, details?.woAssetNo), + labelValueText(context, context.translation.manufacture, details?.woManufacturer), + labelValueText(context, context.translation.model, details?.woModel), + ], + ); + } + + Widget labelValueText(BuildContext context, String label, String? value) { + if (value == null || value.isEmpty) return const SizedBox.shrink(); + + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text( + '$label: $value', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, + ), + ), + ); + } + + Widget checklistWidget({required String value}) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: true, + activeColor: AppColor.neutral120, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity(horizontal: -4, vertical: -3), + onChanged: (value) {}, + ), + value.bodyText(context), + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart new file mode 100644 index 00000000..0d32a4a8 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart @@ -0,0 +1,117 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class SystemInternalAuditItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const SystemInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + //TODO need to refactor this code repetation @waseem + if (requestData != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestData!.statusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!), + ), + 1.width.expanded, + Text( + requestData!.transactionDate?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestData?.typeTransaction ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNo, value: requestData?.assetNumber, context: context), + + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestDetails!.status!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!), + ), + 1.width.expanded, + Text( + requestDetails!.date?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context), + infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context), + infoWidget(label: context.translation.model, value: requestDetails!.model, context: context), + 8.height, + // infoWidget(label: context.translation.site, value: requestDetails!.site, context: context), + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + + Widget infoWidget({required String label, String? value, required BuildContext context}) { + if (value != null && value.isNotEmpty) { + return '$label: $value'.bodyText(context); + } + return const SizedBox(); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart new file mode 100644 index 00000000..1d50b7b8 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart @@ -0,0 +1,316 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/working_time_tile.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; + +class UpdateSystemInternalAuditPage extends StatefulWidget { + static const String id = "update-system-internal-audit"; + SystemInternalAuditDataModel? model; + + UpdateSystemInternalAuditPage({Key? key, this.model}) : super(key: key); + + @override + State createState() => _UpdateSystemInternalAuditPageState(); +} + +class _UpdateSystemInternalAuditPageState extends State { + final bool _isLoading = false; + double totalWorkingHours = 0.0; + AuditFormModel formModel = AuditFormModel(); + final TextEditingController _workingHoursController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + List _attachments = []; + + //TODO need to check if it's needed or not.. + List timerList = []; + + @override + void initState() { + populateForm(); + super.initState(); + } + + void populateForm() { + formModel.requestId = widget.model?.id; + formModel.id = widget.model?.id; + _attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? []; + + calculateWorkingTime(); + } + + void calculateWorkingTime() { + final helperModel = formModel; + final timers = helperModel.auditTimers ?? []; + totalWorkingHours = timers.fold(0.0, (sum, item) { + if (item.startDate == null || item.endDate == null) return sum; + try { + final start = DateTime.parse(item.startDate!); + final end = DateTime.parse(item.endDate!); + final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours + return sum + diffInHours; + } catch (_) { + return sum; + } + }); + timerList = timers.map((e) { + return TimerHistoryModel( + id: e.id, + startTime: e.startDate, + endTime: e.endDate, + workingHours: e.startDate, + ); + }).toList(); + } + + _onSubmit(BuildContext context) async { + bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + InternalAuditProvider provider = Provider.of(context, listen: false); + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + + formModel.auditTimers ??= []; + if (formModel.auditTimePicker != null) { + int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null + endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + } + formModel.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: timer.startAt!.toIso8601String(), // Handle potential null + endDate: timer.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + }); + + _formKey.currentState!.save(); + formModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + formModel.attachments!.add( + InternalAuditAttachments( + id: item.id, + // originalName: fileName, + name: fileName, + ), + ); + } + log('submit press'); + log('data ${formModel.toJson()}'); + final success = await provider.updateSystemInternalAudit(model: formModel); + Navigator.pop(context); + if (formModel.isComplete == true) { + //Navigate to List screen. + } else if (formModel.isComplete == false) { + if (success) { + Navigator.of(context).pop(true); + } else { + Fluttertoast.showToast(msg: 'Failed to update'); + } + } + } + + @override + void dispose() { + _workingHoursController.dispose(); + super.dispose(); + } + + void updateTimer({TimerModel? timer}) { + if (timer?.startAt != null && timer?.endAt != null) { + final start = timer!.startAt!; + final end = timer.endAt!; + + final difference = end.difference(start); + final totalHours = difference.inSeconds / 3600.0; + formModel.startTime = start; + formModel.endTime = end; + formModel.totalHours = totalHours; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'Update Information'.addTranslation, + onWillPopScope: () { + formModel.isComplete = false; + _onSubmit(context); + }, + ), + key: _scaffoldKey, + body: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 8.height, + AppTextFormField( + labelText: 'Debrief'.addTranslation, + textInputType: TextInputType.multiline, + hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + alignLabelWithHint: true, + initialValue: formModel.debrief, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + onSaved: (value) { + formModel.debrief = value; + }, + ), + 8.height, + _timerWidget(context, totalWorkingHours), + 16.height, + AttachmentPicker( + label: 'Upload Attachment', + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset( + color: AppColor.primary10, + ), + ), + 8.height, + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60, + textColor: context.isDark ? AppColor.white10 : AppColor.black10, + onPressed: () { + log('button press '); + formModel.isComplete = false; + _onSubmit(context); + }).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: () { + formModel.isComplete = true; + _onSubmit(context); + }).expanded, + ], + ), + ), + ], + )), + ), + ).handlePopScope( + cxt: context, + onSave: () { + formModel.isComplete = false; + _onSubmit(context); + }); + } + + Widget _timerWidget(BuildContext context, double totalWorkingHours) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ///this is for multiple timers... + // AppTimer( + // label: context.translation.timer, + // timer: formModel.auditTimerModel, + // // enabled: enableTimer, + // pickerTimer: formModel.auditTimePicker, + // pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + // onPick: (time) { + // formModel.auditTimePicker = time; + // }, + // timerProgress: (isRunning) {}, + // onChange: (timer) async { + // formModel.auditTimerModel = timer; + // if (timer.startAt != null && timer.endAt != null) { + // formModel.timerModelList = formModel.timerModelList ?? []; + // formModel.timerModelList!.add(timer); + // } + // return true; + // }, + // ), + AppTimer( + label: context.translation.workingHours, + showPicker: false, + timer: formModel.auditTimerModel, + pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + pickerTimer: formModel.auditTimePicker, + onPick: (time) { + updateTimer(timer: time); + }, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + ), + timerProgress: (isRunning) {}, + onChange: (timer) async { + updateTimer(timer: timer); + log('here onChange ${timer.startAt}'); + + return true; + }, + ), + if (totalWorkingHours > 0.0) ...[ + 12.height, + WorkingTimeTile( + timerList: timerList, + totalWorkingTime: totalWorkingHours, + ), + ], + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart new file mode 100644 index 00000000..e4454551 --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart @@ -0,0 +1,36 @@ + +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditCheckListProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditChecklist); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} + diff --git a/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart new file mode 100644 index 00000000..9371cb1d --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditFindingTypeProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditFindingType); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_provider.dart new file mode 100644 index 00000000..75f0a436 --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_provider.dart @@ -0,0 +1,211 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/models/device/asset_search.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/models/new_models/asset_nd_auto_complete_by_dynamic_codes_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; + +class InternalAuditProvider extends ChangeNotifier { + final pageItemNumber = 10; + final searchPageItemNumber = 10; + int pageNo = 1; + + void reset() { + pageNo = 1; + stateCode = null; + } + + int? stateCode; + bool isDetailLoading = false; + bool nextPage = false; + bool isNextPageLoading = false; + bool isLoading = false; + + Future getEquipmentInternalAuditById(int id) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get( + "${URLs.getInternalAuditEquipmentById}?internalAuditEquipmentId=$id", + ); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + EquipmentInternalAuditDataModel model = EquipmentInternalAuditDataModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future getInternalSystemAuditById(int id) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get("${URLs.getInternalAuditSystemById}?AuditSystemId=$id"); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + SystemInternalAuditDataModel model = SystemInternalAuditDataModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future loadAllWorkOrderDetailsByID({required int workOrderTypeId, required int workOrderId}) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get("${URLs.loadAllWorkOrderDetailsByID}?workOrderTypeId=$workOrderTypeId&workOrderId=$workOrderId"); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + SystemAuditWorkOrderDetailModel model = SystemAuditWorkOrderDetailModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future updateEquipmentInternalAudit({required AuditFormModel model}) async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.put(URLs.updateAuditEquipmentsEngineer, body: model.toJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future updateSystemInternalAudit({required AuditFormModel model}) async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.put(URLs.updateAuditSystemEngineer, body: model.toJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + log('status code ${stateCode}'); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future addEquipmentInternalAudit({ + required BuildContext context, + required EquipmentInternalAuditFormModel request, + }) async { + bool status = false; + Response response; + try { + response = await ApiManager.instance.post(URLs.addOrUpdateEquipmentInternalAudit, body: request.toJson()); + if (response.statusCode >= 200 && response.statusCode < 300) { + status = true; + notifyListeners(); + } else { + Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}"); + status = false; + } + return status; + } catch (error) { + print(error); + status = false; + notifyListeners(); + return status; + } + } + + Future addSystemInternalAudit({ + required BuildContext context, + required SystemInternalAuditFormModel request, + }) async { + bool status = false; + Response response; + try { + response = await ApiManager.instance.post(URLs.addOrUpdateInternalAuditSystem, body: request.toJson()); + if (response.statusCode >= 200 && response.statusCode < 300) { + status = true; + notifyListeners(); + } else { + Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}"); + status = false; + } + return status; + } catch (error) { + print(error); + status = false; + notifyListeners(); + return status; + } + } + + Future> getWorkOrderByWoType({String? text, required int? woId}) async { + late Response response; + try { + Response response = await ApiManager.instance.get("${URLs.getWoAutoComplete}?workOrderTypeId=$woId&search=$text"); + // response = await ApiManager.instance.post(url, body: {"search": text}); + List woOrderList = []; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + woOrderList = categoriesListJson.map((wo) => WoAutoCompleteModel.fromJson(wo)).toList(); + } + return woOrderList; + } catch (error) { + return []; + } + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart new file mode 100644 index 00000000..7c7b4e5e --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart @@ -0,0 +1,35 @@ + +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditWoTypeProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditWoType); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} + diff --git a/lib/modules/tm_module/tasks_wo/update_task_request_view.dart b/lib/modules/tm_module/tasks_wo/update_task_request_view.dart index d25049df..40871d70 100644 --- a/lib/modules/tm_module/tasks_wo/update_task_request_view.dart +++ b/lib/modules/tm_module/tasks_wo/update_task_request_view.dart @@ -271,7 +271,7 @@ class _UpdateTaskRequestState extends State { taskModel?.timerModelList?.forEach((timer) { int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; taskModel.taskJobActivityEngineerTimers?.add( - TaskJobActivityEngineerTimer( + TaskJobActivityEngineerTimer ( id: 0, startDate: timer.startAt!.toIso8601String(), endDate: timer.endAt?.toIso8601String(), totalWorkingHour: ((durationInSecond) / 60 / 60), comment: timer.comments ?? comments), ); }); diff --git a/lib/new_views/pages/land_page/create_request-type_bottomsheet.dart b/lib/new_views/pages/land_page/create_request-type_bottomsheet.dart index b9244e7e..4696c37b 100644 --- a/lib/new_views/pages/land_page/create_request-type_bottomsheet.dart +++ b/lib/new_views/pages/land_page/create_request-type_bottomsheet.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; @@ -8,6 +10,8 @@ import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/models/enums/user_types.dart'; import 'package:test_sa/modules/cm_module/views/nurse/create_new_request_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart'; import 'package:test_sa/modules/loan_module/pages/create_loan_request_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/create_task_view.dart'; import 'package:test_sa/modules/traf_module/create_traf_request_page.dart'; @@ -92,8 +96,11 @@ class CreateRequestModel { static List requestsList(BuildContext context) { List list = []; - - if (context.userProvider.isAssessor) { + log('user type is ${context.userProvider.isQualityUser}'); + if (context.userProvider.isQualityUser) { + list.add(CreateRequestModel("Equipment Internal Audit Checklist".addTranslation, "add_icon", CreateEquipmentInternalAuditForm.id)); + list.add(CreateRequestModel("System Internal Audit Checklist".addTranslation, "add_icon", CreateSystemInternalAuditForm.id)); + } else if (context.userProvider.isAssessor) { list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); } else if (context.userProvider.isEngineer) { if (Provider.of(context, listen: false).engineerCanCreateCM) { diff --git a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart index 9cfce9d4..7305a81a 100644 --- a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart +++ b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart @@ -71,6 +71,8 @@ class _AllRequestsFilterPageState extends State { if (isEngineer) { types[context.translation.recurrentWo] = 5; + types["Equipment Internal Audit".addTranslation] = 10; + types["System Internal Audit".addTranslation] = 11; } if (context.settingProvider.isUserFlowMedical && isEngineer) { @@ -80,9 +82,13 @@ class _AllRequestsFilterPageState extends State { if (!isEngineer) { types['TRAF'] = 9; } + if (context.userProvider.isAssessor) { types = {"TRAF": 9}; } + if (context.userProvider.isQualityUser) { + types = {'Equipment Internal Audit': 10, 'System Internal Audit': 11}; + } final statuses = { "All WO": 0, diff --git a/lib/new_views/pages/land_page/my_request/my_requests_page.dart b/lib/new_views/pages/land_page/my_request/my_requests_page.dart index f24d1719..641c0243 100644 --- a/lib/new_views/pages/land_page/my_request/my_requests_page.dart +++ b/lib/new_views/pages/land_page/my_request/my_requests_page.dart @@ -1,5 +1,7 @@ //request main page +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; @@ -42,9 +44,11 @@ class _MyRequestsPageState extends State { Request(2, context.translation.gasRefill), Request(3, context.translation.transferAsset), Request(4, context.translation.preventiveMaintenance), + ]; if (Provider.of(context, listen: false).user!.type != UsersTypes.normal_user) { requestsList.add(Request(5, context.translation.recurrentWo)); + } //TODO unCommit this to enable task requestsList.add(Request(6, context.translation.taskRequest)); @@ -58,6 +62,11 @@ class _MyRequestsPageState extends State { if (context.userProvider.user!.type == UsersTypes.normal_user) { requestsList.add(Request(9, 'TRAF')); } + if (context.userProvider.isEngineer) { + // TODO need to replace with actual number. + requestsList.add(Request(10, 'Equipment Internal Audit')); + requestsList.add(Request(11, 'System Internal Audit')); + } if (context.userProvider.isAssessor) { requestsList = [ @@ -65,6 +74,14 @@ class _MyRequestsPageState extends State { Request(9, 'TRAF'), ]; } + if (context.userProvider.isQualityUser) { + requestsList = [ + Request(null, context.translation.allWorkOrder), + //TODO need to replace with actual number. + Request(10, 'Equipment Internal Audit'), + Request(11, 'System Internal Audit'), + ]; + } _provider = Provider.of(context, listen: false); _provider!.reset(); diff --git a/lib/new_views/pages/land_page/requests/request_paginated_listview.dart b/lib/new_views/pages/land_page/requests/request_paginated_listview.dart index e436e87d..750abfd7 100644 --- a/lib/new_views/pages/land_page/requests/request_paginated_listview.dart +++ b/lib/new_views/pages/land_page/requests/request_paginated_listview.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; @@ -66,6 +68,10 @@ class RequestPaginatedListview extends StatelessWidget { return InventorySessionItemView(requestData: request); case 9: return TrafRequestItemView(requestData: request); + case 10: + return EquipmentInternalAuditItemView(requestData: request); + case 11: + return SystemInternalAuditItemView(requestData: request); default: return Container( height: 100, diff --git a/lib/new_views/pages/land_page/widgets/request_item_view_list.dart b/lib/new_views/pages/land_page/widgets/request_item_view_list.dart index b84406cc..b8d1f6eb 100644 --- a/lib/new_views/pages/land_page/widgets/request_item_view_list.dart +++ b/lib/new_views/pages/land_page/widgets/request_item_view_list.dart @@ -1,12 +1,17 @@ //request tab page +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/pages/land_page/requests/device_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/gas_refill_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/ppm_item_view.dart'; @@ -29,9 +34,19 @@ class RequestItemViewList extends StatelessWidget { shrinkWrap: true, itemBuilder: (cxt, index) { if (isLoading) return const SizedBox().toRequestShimmer(cxt, isLoading); + // if (list[index].transactionType == null) { + // return EquipmentInternalAuditItemView(requestDetails: list[index]); + // // return Container( + // // height: 100, + // // width: double.infinity, + // // color: AppColor.neutral40, + // // ); + // } switch (list[index].transactionType) { case 1: return ServiceRequestItemView(requestDetails: list[index]); + // case 1: + // return InternalAuditItemView(requestDetails: list[index]); case 2: return GasRefillItemView(requestDetails: list[index]); case 3: @@ -48,6 +63,11 @@ class RequestItemViewList extends StatelessWidget { return InventorySessionItemView(requestDetails: list[index]); case 9: return TrafRequestItemView(requestDetails: list[index]); + //Need to verify the type No.. + case 10: + return EquipmentInternalAuditItemView(requestDetails: list[index]); + case 11: + return SystemInternalAuditItemView(requestDetails: list[index]); default: Container( height: 100, diff --git a/lib/new_views/pages/login_page.dart b/lib/new_views/pages/login_page.dart index 8c54e927..946eda54 100644 --- a/lib/new_views/pages/login_page.dart +++ b/lib/new_views/pages/login_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -12,6 +13,8 @@ import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/new_models/general_response_model.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_page.dart'; +import 'package:test_sa/modules/cx_module/survey/survey_page.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/forget_password_module/forget_passwod_verify_otp.dart'; import 'package:test_sa/new_views/pages/land_page/land_page.dart'; @@ -225,6 +228,8 @@ class _LoginPageState extends State { } Future _login() async { + // Navigator.push(context, CupertinoPageRoute(builder: (context) => ChatPage(moduleId: 1, requestId: 1845972))); + // return; if (!_formKey.currentState!.validate()) return; if (privacyPolicyChecked == false) { diff --git a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart index 7b76d715..60db0c03 100644 --- a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart +++ b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart @@ -17,7 +17,7 @@ class ConfirmDialog extends StatelessWidget { @override Widget build(BuildContext context) { return Dialog( - backgroundColor:AppColor.background(context), + backgroundColor: AppColor.background(context), shape: const RoundedRectangleBorder(), insetPadding: const EdgeInsets.only(left: 21, right: 21), child: Padding( @@ -32,7 +32,7 @@ class ConfirmDialog extends StatelessWidget { Expanded( child: Text( title ?? "Confirm", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), ).paddingOnly(top: 16), ), IconButton( diff --git a/lib/views/pages/user/gas_refill/gas_refill_details.dart b/lib/views/pages/user/gas_refill/gas_refill_details.dart index e5964bc5..3773c898 100644 --- a/lib/views/pages/user/gas_refill/gas_refill_details.dart +++ b/lib/views/pages/user/gas_refill/gas_refill_details.dart @@ -11,6 +11,7 @@ import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_widget.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/views/pages/user/gas_refill/update_gas_refill_request.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; @@ -52,10 +53,29 @@ class _GasRefillDetailsPageState extends State { @override Widget build(BuildContext context) { _userProvider = Provider.of(context); - gasRefillProvider = Provider.of(context); + gasRefillProvider = Provider.of(context, listen: false); return Scaffold( - appBar: DefaultAppBar(title: context.translation.gasRefillDetails), + appBar: DefaultAppBar( + title: context.translation.gasRefillDetails, + actions: [ + // if (context.userProvider.isEngineer || context.userProvider.isNurse) + // Selector( + // selector: (_, myModel) => myModel.gasRefillModel, // Selects only the userName + // builder: (_, _gasRefillModel, __) { + // if (_gasRefillModel == null) return const SizedBox(); + // return ChatWidget( + // moduleId: 2, + // isShow: _gasRefillModel.status!.value! == 1, + // // isReadOnly: _gasRefillModel.status!.value! == 1, + // requestId: widget.model.id!.toInt(), + // assigneeEmployeeNumber: _gasRefillModel.assignedEmployee!.name!, + // myLoginUserID: context.userProvider.user!.username!, + // contactEmployeeINumber: _gasRefillModel.gasRefillContactPerson!.first.employeeCode!, + // ); + // }) + ], + ), key: _scaffoldKey, body: FutureBuilder( future: gasRefillProvider.getGasRefillObjectById(widget.model.id!), diff --git a/lib/views/pages/user/notifications/notifications_page.dart b/lib/views/pages/user/notifications/notifications_page.dart index 47b64d00..8c551b45 100644 --- a/lib/views/pages/user/notifications/notifications_page.dart +++ b/lib/views/pages/user/notifications/notifications_page.dart @@ -37,12 +37,11 @@ class _NotificationsPageState extends State with TickerProvid stateCode: _notificationsProvider.stateCode, onRefresh: () async { _notificationsProvider.reset(); - await _notificationsProvider.getSystemNotifications( - user: _userProvider.user!,resetProvider: true + await _notificationsProvider.getSystemNotifications(user: _userProvider.user!, resetProvider: true - // host: _settingProvider.host, - // hospitalId: _userProvider.user.clientId, - ); + // host: _settingProvider.host, + // hospitalId: _userProvider.user.clientId, + ); }, child: NotificationsList( nextPage: _notificationsProvider.nextPage, diff --git a/lib/views/widgets/dialogs/loading_dialog.dart b/lib/views/widgets/dialogs/loading_dialog.dart new file mode 100644 index 00000000..1a72e452 --- /dev/null +++ b/lib/views/widgets/dialogs/loading_dialog.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +class UploadingDialog extends StatelessWidget { + const UploadingDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + height: 150.toScreenWidth, + width: 200.toScreenWidth, + alignment: Alignment.center, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.neutral30, + borderRadius: BorderRadius.circular(20.0), + boxShadow: [AppStyle.boxShadow], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(strokeWidth: 3), + 16.height, + Text( + "Uploading...", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), + ) + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/notifications/notification_item.dart b/lib/views/widgets/notifications/notification_item.dart index 89fef4b5..71135557 100644 --- a/lib/views/widgets/notifications/notification_item.dart +++ b/lib/views/widgets/notifications/notification_item.dart @@ -28,14 +28,14 @@ class NotificationItem extends StatelessWidget { label: notification.priorityName, textColor: AppColor.getRequestStatusTextColorByName(context, notification.priorityName!), backgroundColor: AppColor.getRequestStatusColorByName(context, notification.priorityName!), - ).toShimmer(isShow: isLoading,context: context), + ).toShimmer(isShow: isLoading, context: context), 8.width, if ((notification.statusName ?? "").isNotEmpty && notification.sourceName != "Asset Transfer") StatusLabel( label: notification.statusName ?? "", textColor: AppColor.getRequestStatusTextColorByName(context, notification.statusName ?? ""), backgroundColor: AppColor.getRequestStatusColorByName(context, notification.statusName ?? ""), - ).toShimmer(isShow: isLoading,context: context), + ).toShimmer(isShow: isLoading, context: context), ], ), 8.height, @@ -47,7 +47,7 @@ class NotificationItem extends StatelessWidget { style: AppTextStyles.heading6.copyWith( color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, ), - ).toShimmer(isShow: isLoading,context: context).expanded, + ).toShimmer(isShow: isLoading, context: context).expanded, 8.width, Text( notification.createdOn?.toServiceRequestCardFormat ?? "", @@ -55,7 +55,7 @@ class NotificationItem extends StatelessWidget { style: AppTextStyles.tinyFont.copyWith( color: context.isDark ? AppColor.neutral20 : AppColor.neutral50, ), - ).toShimmer(isShow: isLoading,context: context), + ).toShimmer(isShow: isLoading, context: context), ], ), Text( @@ -63,7 +63,7 @@ class NotificationItem extends StatelessWidget { style: AppTextStyles.bodyText2.copyWith( color: context.isDark ? AppColor.neutral10 : const Color(0xFF757575), ), - ).toShimmer(isShow: isLoading,context: context), + ).toShimmer(isShow: isLoading, context: context), ], ).onPress(() { onPressed(notification); diff --git a/lib/views/widgets/sound/sound_player.dart b/lib/views/widgets/sound/sound_player.dart index edf8b377..a9368299 100644 --- a/lib/views/widgets/sound/sound_player.dart +++ b/lib/views/widgets/sound/sound_player.dart @@ -7,8 +7,9 @@ import 'package:test_sa/new_views/app_style/app_text_style.dart'; class ASoundPlayer extends StatefulWidget { final String audio; + final bool showLoading; - const ASoundPlayer({Key? key, required this.audio}) : super(key: key); + const ASoundPlayer({Key? key, required this.audio, this.showLoading = false}) : super(key: key); @override _ASoundPlayerState createState() => _ASoundPlayerState(); @@ -161,6 +162,7 @@ class _ASoundPlayerState extends State { if (_isLocalFile) { await _audioPlayer.setSourceDeviceFile(_audio); } else { + // await _audioPlayer.setSource(UrlSource(_audio, headers: widget.headers)); await _audioPlayer.setSourceUrl(_audio); } _audioPlayer.seek(const Duration(milliseconds: 0)); diff --git a/lib/views/widgets/timer/app_timer.dart b/lib/views/widgets/timer/app_timer.dart index 84179d92..629ad60c 100644 --- a/lib/views/widgets/timer/app_timer.dart +++ b/lib/views/widgets/timer/app_timer.dart @@ -22,6 +22,7 @@ class AppTimer extends StatefulWidget { final TextStyle? style; final BoxDecoration? decoration; final bool enabled; + final bool showPicker; final String? label; final double? width; @@ -40,6 +41,7 @@ class AppTimer extends StatefulWidget { this.onPick, this.timerProgress, this.enabled = true, + this.showPicker = true, }) : super(key: key); @override @@ -126,7 +128,8 @@ class _AppTimerState extends State { @override void initState() { - canPickTime = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + ///this is temp check for internal audit + canPickTime = widget.showPicker == false ? false : ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; _startAt = widget.timer?.startAt; _endAt = widget.timer?.endAt; _running = _startAt != null && _endAt == null; diff --git a/pubspec.lock b/pubspec.lock index 902236f8..44813421 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.23" + audio_waveforms: + dependency: "direct main" + description: + name: audio_waveforms + sha256: "658fef41bbab299184b65ba2fd749e8ec658c1f7d54a21f7cf97fa96b173b4ce" + url: "https://pub.dev" + source: hosted + version: "1.3.0" audioplayers: dependency: "direct main" description: @@ -941,6 +949,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" lottie: dependency: "direct main" description: @@ -965,6 +981,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + message_pack_dart: + dependency: transitive + description: + name: message_pack_dart + sha256: "71b9f0ff60e5896e60b337960bb535380d7dba3297b457ac763ccae807385b59" + url: "https://pub.dev" + source: hosted + version: "2.0.1" meta: dependency: transitive description: @@ -1285,6 +1309,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.2+1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" provider: dependency: "direct main" description: @@ -1421,6 +1453,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" shimmer: dependency: "direct main" description: @@ -1429,6 +1469,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + signalr_netcore: + dependency: "direct main" + description: + name: signalr_netcore + sha256: "8d59dc61284c5ff8aa27c4e3e802fcf782367f06cf42b39d5ded81680b72f8b8" + url: "https://pub.dev" + source: hosted + version: "1.4.4" signature: dependency: "direct main" description: @@ -1498,6 +1546,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.4+4" + sse: + dependency: transitive + description: + name: sse + sha256: fcc97470240bb37377f298e2bd816f09fd7216c07928641c0560719f50603643 + url: "https://pub.dev" + source: hosted + version: "4.1.8" + sse_channel: + dependency: transitive + description: + name: sse_channel + sha256: "9aad5d4eef63faf6ecdefb636c0f857bd6f74146d2196087dcf4b17ab5b49b1b" + url: "https://pub.dev" + source: hosted + version: "0.1.1" stack_trace: dependency: transitive description: @@ -1578,6 +1642,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: @@ -1714,6 +1786,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" wifi_iot: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e7584098..a7689979 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,8 @@ dependencies: toggle_switch: ^2.3.0 clipboard: ^2.0.2 tree_view_flutter: ^1.0.2 + audio_waveforms: ^1.3.0 + signalr_netcore: ^1.4.4 local_auth_darwin: any dev_dependencies: