From 8d60f4c81e3fac91ffeb8f341c2d8c12eb764be6 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Apr 2026 10:38:18 +0300 Subject: [PATCH 01/73] improvements. --- .../pages/cleaner_search_details_view.dart | 106 +++++++++--------- .../pages/cleaner_search_list_view.dart | 5 +- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart index 65d7b525..5d20de82 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart @@ -6,12 +6,14 @@ 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/ppm/recurrent_wo.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/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/views/widgets/qr/scan_qr.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; import '../models/cleaner_task_search_result.dart'; import '../provider/cleaner_provider.dart'; import '../../pm_module/recurrent_wo/recurrent_work_order_view.dart'; @@ -34,8 +36,12 @@ class CleanerSearchDetailsView extends StatefulWidget { class _CleanerSearchDetailsViewState extends State { late CleanerProvider _cleanerProvider; - bool _isQRScanned = false; + + // bool _isQRScanned = false; + + bool _isRequestOpen = false; CleanerTaskSearchResult? _taskData; + RecurrentWoData? _recurrentWoData; bool _isLoading = false; @override @@ -61,17 +67,18 @@ class _CleanerSearchDetailsViewState extends State { // Map RecurrentWoData to CleanerTaskSearchResult if (allRequestsProvider.recurrentWoData != null) { - final recurrentData = allRequestsProvider.recurrentWoData!; + _recurrentWoData = allRequestsProvider.recurrentWoData!; + _isRequestOpen = _recurrentWoData?.status?.value == 4; _taskData = CleanerTaskSearchResult( - id: recurrentData.id, - taskNo: recurrentData.taskNo, - siteName: recurrentData.site?.siteName, - buildingName: recurrentData.building?.name, - floorName: recurrentData.floor?.name, - departmentName: recurrentData.department?.name, - roomName: recurrentData.room?.name, + id: _recurrentWoData!.id, + taskNo: _recurrentWoData!.taskNo, + siteName: _recurrentWoData!.site?.siteName, + buildingName: _recurrentWoData!.building?.name, + floorName: _recurrentWoData!.floor?.name, + departmentName: _recurrentWoData!.department?.name, + roomName: _recurrentWoData!.room?.name, locationDescription: null, - createdDate: recurrentData.scheduleDate, + createdDate: _recurrentWoData!.scheduleDate, ); } } catch (e) { @@ -126,7 +133,7 @@ class _CleanerSearchDetailsViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (!_isQRScanned) ...[ + if (_isRequestOpen) ...[ Row( children: [ Icon(Icons.info_outline_rounded, size: 16, color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), @@ -135,7 +142,7 @@ class _CleanerSearchDetailsViewState extends State { child: Text( 'By scanning the QR code you will accept this task'.addTranslation, // textAlign: TextAlign.center, - style: AppTextStyles.bodyText.copyWith( + style: AppTextStyles.bodyText2.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ), @@ -145,53 +152,27 @@ class _CleanerSearchDetailsViewState extends State { 8.height, ], AppFilledButton( - label: _isQRScanned ? 'Update' : 'Scan QR Code', + label: _isRequestOpen ? 'Scan QR Code' : 'Update', maxWidth: true, buttonColor: AppColor.primary10, onPressed: () async { - if (_isQRScanned) { - // If QR is already scanned, navigate to recurrent work order view - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId), - ), - ); - } else { - // Open QR scanner - final result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ScanQr(), - ), - ); - - // If QR code was scanned successfully, check if it matches the room + if (_isRequestOpen) { + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanQr())); if (result != null && result is String && result.isNotEmpty) { - // Show loading indicator - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const AppLazyLoading(), - ); - - // Call the API to check if QR matches the same room - final isValid = await CleanerProvider().checkQRSameRoom( - planRecurrentTaskId: widget.requestId, - qrCode: result, - ); - - // Close loading dialog + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + final isValid = await CleanerProvider().checkQRSameRoom(planRecurrentTaskId: widget.requestId, qrCode: result); if (mounted) Navigator.pop(context); if (isValid) { - // Update state to show "Update" button instead of navigating if (mounted) { - setState(() { - _isQRScanned = true; - }); + getInitialData(); } } } + } else { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), + ); } }, ), @@ -230,12 +211,24 @@ class _CleanerSearchDetailsViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Request Information', - style: AppTextStyles.heading4.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + // Text( + // 'Request Information', + // style: AppTextStyles.heading4.copyWith( + // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + // ), + // ), + if (_recurrentWoData?.status?.name != null) + StatusLabel( + label: _recurrentWoData?.status?.name!, + textColor: AppColor.getRequestStatusTextColorByName(context, _recurrentWoData?.status?.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, _recurrentWoData?.status?.name!), ), - ), + 8.width, + // StatusLabel( + // label: taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!, + // textColor: AppColor.getRequestStatusTextColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), + // backgroundColor: AppColor.getRequestStatusColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), + // ), if (taskResult.createdDate != null) Text( taskResult.createdDate!.toServiceRequestCardFormat, @@ -245,6 +238,13 @@ class _CleanerSearchDetailsViewState extends State { ], ), 8.height, + if (_recurrentWoData?.title?.isNotEmpty ?? false) + Text( + _recurrentWoData?.title ?? "", + style: AppTextStyles.heading4.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), if (taskResult.taskNo != null && taskResult.taskNo!.isNotEmpty) Text( 'Task No: ${taskResult.taskNo!}', diff --git a/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart index 884ba220..87e25e22 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart @@ -167,10 +167,7 @@ class _CleanerSearchListViewState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => CleanerSearchDetailsView( - requestId: result.id ?? 0, - taskResult: result, - ), + builder: (context) => CleanerSearchDetailsView(requestId: result.id ?? 0, taskResult: null), ), ); }, From d242b190ce436081cefdbece17c98cf88cf23c60 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Apr 2026 16:31:54 +0300 Subject: [PATCH 02/73] improvements. --- .../pages/cleaner_search_details_view.dart | 6 +- .../widgets/cleaner_checklist_widget.dart | 62 +++++++++++-------- .../components/task_info_widget.dart | 30 ++++++--- .../recurrent_work_order_view.dart | 5 +- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart index 5d20de82..03e2ee38 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart @@ -165,7 +165,11 @@ class _CleanerSearchDetailsViewState extends State { if (isValid) { if (mounted) { - getInitialData(); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), + ); + + // getInitialData(); } } } diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 9f6f117b..ad3cb08b 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -45,8 +45,15 @@ class _CleanerChecklistWidgetState extends State { ), ), 16.height, - ...widget.checklistItems.map((item) => _buildChecklistItem(context, item)).toList(), - // 16.height, + ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (cxt, index) => _buildChecklistItem(context, widget.checklistItems[index]), + separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), + itemCount: widget.checklistItems.length) + .toShadowContainer(context, padding: 16), + 16.height, _buildImageCaptureWidget(context), ], ); @@ -59,28 +66,21 @@ class _CleanerChecklistWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // Checkbox with title - Row( - children: [ - Checkbox( - // visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ListTileTheme( + horizontalTitleGap: 0, + child: Container( + alignment: Alignment.center, + height: 50.toScreenHeight, + child: CheckboxListTile( value: item.isChecked, + controlAffinity: ListTileControlAffinity.leading, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + contentPadding: EdgeInsets.zero, + checkboxScaleFactor: 1.2, + side: BorderSide(color: Colors.black.withValues(alpha: .05)), + visualDensity: const VisualDensity(horizontal: -0.0, vertical: -4.0), activeColor: AppColor.primary10, - onChanged: (bool? value) { - setState(() { - item.isChecked = value ?? false; - if (!item.isChecked) { - item.commentController.clear(); - } - }); - // Notify parent of changes - widget.onChecklistChanged?.call(widget.checklistItems); - }, - ), - 8.width, - Expanded( - child: RichText( + title: RichText( text: TextSpan( children: [ TextSpan( @@ -101,13 +101,21 @@ class _CleanerChecklistWidgetState extends State { ], ), ), + onChanged: (bool? value) { + setState(() { + item.isChecked = value ?? false; + if (!item.isChecked) { + item.commentController.clear(); + } + }); + // Notify parent of changes + widget.onChecklistChanged?.call(widget.checklistItems); + }, ), - ], + ), ), - - // Comment box - only visible when checked if (item.isChecked) ...[ - 16.height, + // 16.height, AppTextFormField( controller: item.commentController, labelText: context.translation.comment, @@ -126,7 +134,7 @@ class _CleanerChecklistWidgetState extends State { ), ], ], - ).toShadowContainer(context).paddingOnly(bottom: 16), + ), ); } diff --git a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart index 22a6477a..d49be1bc 100644 --- a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart +++ b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart @@ -19,8 +19,9 @@ import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart class RecurrentTaskInfoWidget extends StatefulWidget { final RecurrentWoData? model; bool isClosed; + bool isHouseKeeper; - RecurrentTaskInfoWidget({super.key, required this.model, this.isClosed = false}); + RecurrentTaskInfoWidget({super.key, required this.model, this.isClosed = false, this.isHouseKeeper = false}); @override State createState() => _RecurrentTaskInfoWidgetState(); @@ -81,6 +82,11 @@ class _RecurrentTaskInfoWidgetState extends State { '${context.translation.taskNo}: ${widget.model!.taskNo!}'.bodyText2(context).custom(color: AppColor.neutral120), 'Plan Name: ${widget.model?.planRecurrent?.planName ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), '${context.translation.site}: ${widget.model!.site!.siteName!}'.bodyText2(context).custom(color: AppColor.neutral120), + if (widget.isHouseKeeper) ...[ + '${context.translation.department}: ${widget.model!.department?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), + '${context.translation.floor}: ${widget.model!.floor?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), + '${context.translation.room}: ${widget.model!.room?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), + ], '${context.translation.assignedEmployee}: ${widget.model!.engineer!.userName ?? ""}'.bodyText2(context).custom(color: AppColor.neutral120), '${context.translation.scheduledDate}: ${widget.model!.scheduleDate!.toMonthYearFormat}'.bodyText2(context).custom(color: AppColor.neutral120), ], @@ -91,16 +97,20 @@ class _RecurrentTaskInfoWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildingInfoWidget(label: context.translation.department, value: widget.model!.department?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context) - .ignorePointer(widget.isClosed), - 8.height, - buildingInfoWidget(label: context.translation.floor, value: widget.model!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context).ignorePointer(widget.isClosed), - 8.height, - buildingInfoWidget(label: context.translation.room, value: widget.model!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context).ignorePointer(widget.isClosed), - 8.height, + if (!widget.isHouseKeeper) ...[ + buildingInfoWidget(label: context.translation.department, value: widget.model!.department?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context) + .ignorePointer(widget.isClosed), + 8.height, + buildingInfoWidget(label: context.translation.floor, value: widget.model!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context).ignorePointer(widget.isClosed), + 8.height, + buildingInfoWidget(label: context.translation.room, value: widget.model!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, context: context).ignorePointer(widget.isClosed), + 8.height, + ], _timerWidget(context, totalWorkingHours), - 8.height, - _commentWidget(context: context, model: widget.model).ignorePointer(widget.isClosed), + if (!widget.isHouseKeeper) ...[ + 8.height, + _commentWidget(context: context, model: widget.model).ignorePointer(widget.isClosed), + ], ], // ], ).toShadowContainer(context, padding: 12) diff --git a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart index 800b19f0..8f99d03c 100644 --- a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart +++ b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart @@ -56,6 +56,7 @@ class _RecurrentWorkOrderViewState extends State { Widget build(BuildContext context) { return Consumer(builder: (context, requestProvider, child) { isClosed = requestProvider.recurrentWoData?.status?.value == 1; + bool isHouseKeeper = requestProvider.recurrentWoData!.planRecurrentTaskCheckLists != null && requestProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty; return Scaffold( appBar: DefaultAppBar( title: context.translation.recurrentWo, @@ -77,7 +78,7 @@ class _RecurrentWorkOrderViewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - RecurrentTaskInfoWidget(isClosed: isClosed,model: requestProvider.recurrentWoData), + RecurrentTaskInfoWidget(isClosed: isClosed, model: requestProvider.recurrentWoData, isHouseKeeper: isHouseKeeper), IgnorePointer( ignoring: isClosed, child: requestProvider.recurrentWoData!.planRecurrentMedicalTaskRooms != null && requestProvider.recurrentWoData!.planRecurrentMedicalTaskRooms!.isNotEmpty @@ -90,7 +91,7 @@ class _RecurrentWorkOrderViewState extends State { : const SizedBox(), ), 16.height, - requestProvider.recurrentWoData!.planRecurrentTaskCheckLists != null && requestProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty + isHouseKeeper ? CleanerChecklistWidget( isClosed: isClosed, checklistItems: requestProvider.recurrentWoData!.planRecurrentTaskCheckLists ?? [], From 515da156b2b932f29b9aaed22ad2265f34b75ace Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 28 Apr 2026 09:14:06 +0300 Subject: [PATCH 03/73] improvements. --- .../widgets/cleaner_checklist_widget.dart | 64 ++++++++++++++++--- .../recurrent_work_order_view.dart | 17 +++-- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index ad3cb08b..9ec79ab6 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -50,7 +50,7 @@ class _CleanerChecklistWidgetState extends State { padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), itemBuilder: (cxt, index) => _buildChecklistItem(context, widget.checklistItems[index]), - separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), + separatorBuilder: (cxt, index) => const Divider(height: 1, color: AppColor.neutral30, thickness: 1).paddingOnly(top: 16, bottom: 16), itemCount: widget.checklistItems.length) .toShadowContainer(context, padding: 16), 16.height, @@ -66,13 +66,63 @@ class _CleanerChecklistWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - ListTileTheme( + Row( + children: [ + AbsorbPointer( + absorbing: true, + child: Transform.scale( + scale: 1.2, + child: Checkbox( + value: item.isChecked, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + side: BorderSide(color: Colors.black.withValues(alpha: .05)), + visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), + activeColor: AppColor.primary10, + onChanged: (bool? value) {}, + ), + ), + ), + 10.width, + RichText( + text: TextSpan( + children: [ + TextSpan( + text: item.title, + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w500, + ), + ), + if (item.isMandatory) + TextSpan( + text: ' *', + style: AppTextStyles.bodyText.copyWith( + color: Colors.red, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ).expanded, + ], + ).onPress(() { + setState(() { + item.isChecked = !item.isChecked; + if (!item.isChecked) { + item.commentController.clear(); + } + }); + widget.onChecklistChanged?.call(widget.checklistItems); + }), + /* ListTileTheme( horizontalTitleGap: 0, + contentPadding: EdgeInsets.zero, + minVerticalPadding: 0, child: Container( - alignment: Alignment.center, - height: 50.toScreenHeight, + color: Colors.amberAccent, child: CheckboxListTile( value: item.isChecked, + minVerticalPadding: 0, controlAffinity: ListTileControlAffinity.leading, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, contentPadding: EdgeInsets.zero, @@ -108,14 +158,13 @@ class _CleanerChecklistWidgetState extends State { item.commentController.clear(); } }); - // Notify parent of changes widget.onChecklistChanged?.call(widget.checklistItems); }, ), ), - ), + ),*/ if (item.isChecked) ...[ - // 16.height, + 12.height, AppTextFormField( controller: item.commentController, labelText: context.translation.comment, @@ -128,7 +177,6 @@ class _CleanerChecklistWidgetState extends State { textInputType: TextInputType.multiline, onChange: (value) { item.comment = value; - // Notify parent of changes widget.onChecklistChanged?.call(widget.checklistItems); }, ), diff --git a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart index 8f99d03c..3b1a0a30 100644 --- a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart +++ b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart @@ -186,14 +186,19 @@ void _updateTask({required BuildContext context, required int status}) async { ), ); }); - log('payload for update is ${allRequestsProvider.recurrentWoData?.toJson()}'); + + final uniqueTimersMap = {}; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.forEach((timer) { + if (timer.startTime != null && timer.endTime != null) { + final key = '${timer.id}_${timer.startTime}_${timer.endTime}'; + uniqueTimersMap[key] = timer; + } + }); + + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = uniqueTimersMap.values.toList(); showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - await allRequestsProvider - .updateRecurrentWo( - status: status, - ) - .then((success) { + await allRequestsProvider.updateRecurrentWo(status: status).then((success) { Navigator.pop(context); // Close loading dialog if (success) { if (status == 1) { From d1a94df33d5e9cf9b380ba9e968a0ce8e43eca23 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 28 Apr 2026 10:11:23 +0300 Subject: [PATCH 04/73] improvements. --- lib/controllers/providers/api/all_requests_provider.dart | 9 ++++----- .../cleaner_module/pages/cleaner_request_item_view.dart | 3 ++- .../tm_module/device_transfer/device_filter_screen.dart | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index de05fc92..435af277 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -273,21 +273,20 @@ class AllRequestsProvider extends ChangeNotifier { } Future updateRecurrentWo({required int status}) async { - isLoading = true; Response response; try { response = await ApiManager.instance.post(URLs.updateRecurrentPlanUrl, body: recurrentWoData!.toJson(status: status)); stateCode = response.statusCode; - isLoading = false; - notifyListeners(); + // isLoading = false; + // notifyListeners(); if (stateCode == 200) { return true; } return false; } catch (error) { - isLoading = false; + // isLoading = false; stateCode = -1; - notifyListeners(); + // notifyListeners(); return false; } } diff --git a/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart b/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart index 96d0eb05..0ffc8e96 100644 --- a/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart @@ -1,6 +1,7 @@ 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/new_views/app_style/app_color.dart'; @@ -26,7 +27,7 @@ class CleanerRequestItemView extends StatelessWidget { if (result.taskNo != null && result.taskNo!.isNotEmpty) result.taskNo!.heading5(context), if (result.createdDate != null) Text( - result.createdDate!.split('T').first, + result.createdDate!.toServiceRequestCardFormat, textAlign: TextAlign.end, style: AppTextStyles.tinyFont.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral50, diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index b0cf860f..3c89a299 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -126,7 +126,7 @@ class _DeviceFilterScreenState extends State { Row( children: [ ADatePicker( - label: context.translation.to, + label: context.translation.from, from: DateTime(2000), date: DateTime.tryParse(startDate ?? ""), onDatePicker: (date) { From d3dadb70c3e5b5ad243b00ea546d248f585f229f Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 5 May 2026 11:05:08 +0300 Subject: [PATCH 05/73] improvements --- .../providers/api/all_requests_provider.dart | 27 +- .../providers/api/user_provider.dart | 1 + lib/dashboard_latest/dashboard_provider.dart | 10 + .../widgets/progress_fragment.dart | 11 +- .../widgets/request_category_fragment.dart | 6 +- lib/models/all_requests_and_count_model.dart | 79 +-- lib/models/enums/user_types.dart | 3 +- lib/models/new_models/dashboard_count.dart | 5 +- lib/models/new_models/dashboard_detail.dart | 28 +- lib/models/user.dart | 2 + .../pages/cleaner_search_details_view.dart | 145 +++++- .../pages/cleaner_search_form_view.dart | 20 +- .../provider/cleaner_provider.dart | 44 ++ .../widgets/cleaner_preview_dialog.dart | 457 ++++++++++++++++++ .../components/task_info_widget.dart | 117 ++++- .../recurrent_work_order_view.dart | 288 ++++++++--- .../my_request/all_requests_filter_page.dart | 17 +- .../my_request/my_requests_page.dart | 8 + .../requests/recurrent_wo_item_view.dart | 23 +- lib/views/widgets/dialogs/confirm_dialog.dart | 1 + 20 files changed, 1141 insertions(+), 151 deletions(-) create mode 100644 lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 435af277..c3d244d3 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -176,6 +176,10 @@ class AllRequestsProvider extends ChangeNotifier { // list = [16]; list.add(16); } + if ( context.userProvider.isHouseKeeper) { + list = [5]; + } + return list; } @@ -272,22 +276,27 @@ class AllRequestsProvider extends ChangeNotifier { } } - Future updateRecurrentWo({required int status}) async { + Future> updateRecurrentWo({required int status, bool showToast = true}) async { Response response; try { - response = await ApiManager.instance.post(URLs.updateRecurrentPlanUrl, body: recurrentWoData!.toJson(status: status)); + response = await ApiManager.instance.post(URLs.updateRecurrentPlanUrl, body: recurrentWoData!.toJson(status: status), showToast: showToast); stateCode = response.statusCode; - // isLoading = false; - // notifyListeners(); + if (stateCode == 200) { - return true; + return {'success': true, 'message': ''}; + } else { + // Parse error message from response + try { + final responseBody = jsonDecode(response.body); + String errorMessage = responseBody['message'] ?? responseBody['title'] ?? 'Failed to update the task'; + return {'success': false, 'message': errorMessage}; + } catch (e) { + return {'success': false, 'message': 'Failed to update the task. Status code: $stateCode'}; + } } - return false; } catch (error) { - // isLoading = false; stateCode = -1; - // notifyListeners(); - return false; + return {'success': false, 'message': error.toString()}; } } diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index ec6f3242..846282cf 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -51,6 +51,7 @@ class UserProvider extends ChangeNotifier { bool get isQualityUser => user!.type == UsersTypes.qualityUser; bool get isCommissioningEngineer => user!.type == UsersTypes.commissioningEngineer; + bool get isHouseKeeper => user!.type == UsersTypes.houseKeeper; VerifyOtpModel _verifyOtpModel = VerifyOtpModel(); SwipeTransaction _swipeTransactionModel = SwipeTransaction(); diff --git a/lib/dashboard_latest/dashboard_provider.dart b/lib/dashboard_latest/dashboard_provider.dart index 03ca5104..92ecd60e 100644 --- a/lib/dashboard_latest/dashboard_provider.dart +++ b/lib/dashboard_latest/dashboard_provider.dart @@ -118,6 +118,7 @@ class DashBoardProvider extends ChangeNotifier { UsersTypes.qualityUser: URLs.qaAuditorCount, UsersTypes.commissioningEngineer: URLs.commissioningEngineerCount, UsersTypes.engineer: URLs.engineerDashboardCountUrl, + UsersTypes.houseKeeper: URLs.engineerDashboardCountUrl, UsersTypes.assessor: URLs.engineerDashboardCountUrl, UsersTypes.assessorTl: URLs.engineerDashboardCountUrl, }; @@ -257,6 +258,7 @@ class DashBoardProvider extends ChangeNotifier { case UsersTypes.engineer: case UsersTypes.assessor: case UsersTypes.assessorTl: + case UsersTypes.houseKeeper: if (status == 0) { return URLs.engineerDashboardUpcoming; } else if (status == 1 && tabId == 1) { @@ -413,6 +415,14 @@ class CategoryTabs { tabs.add(CategoryTabs(label: 'Completed', tag: 3, id: 4)); return tabs; } + if (userType == UsersTypes.houseKeeper) { + // tabs.add(CategoryTabs(label: 'Upcoming', tag: 0, id: 0)); + // tabs.add(CategoryTabs('Open', 1)); + tabs.add(CategoryTabs(label: 'UnAssigned', tag: 1, id: 1)); + tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 2)); + tabs.add(CategoryTabs(label: 'Closed', tag: 4, id: 3)); + return tabs; + } if (userType == UsersTypes.qualityUser) { tabs.add(CategoryTabs(label: 'Open', tag: 1, id: 2)); diff --git a/lib/dashboard_latest/widgets/progress_fragment.dart b/lib/dashboard_latest/widgets/progress_fragment.dart index 4ab32aab..ed7443c2 100644 --- a/lib/dashboard_latest/widgets/progress_fragment.dart +++ b/lib/dashboard_latest/widgets/progress_fragment.dart @@ -183,12 +183,19 @@ class ProgressFragment extends StatelessWidget { ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.orange70), ]; } else if (_userProvider.isQualityUser) { - total = (snapshot.dashboardCount?.data?.countComplete ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); + total = (snapshot.dashboardCount?.data?.countClose ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); chartData = [ - ChartData('Closed', snapshot.dashboardCount?.data?.countComplete?.toDouble() ?? 0.0, AppColor.green70), + ChartData('Closed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.green70), ChartData('Open', snapshot.dashboardCount?.data?.countOpen?.toDouble() ?? 0.0, AppColor.blueStatus(context)), ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.orange70), ]; + } else if (_userProvider.isHouseKeeper) { + total = (snapshot.dashboardCount?.data?.countClose ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); + chartData = [ + ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.orange70), + ChartData('Closed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.green70), + // ChartData('Open', snapshot.dashboardCount?.data?.countOpen?.toDouble() ?? 0.0, AppColor.blueStatus(context)), + ]; } else { total = (snapshot.dashboardCount?.data?.countComplete ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); chartData = [ diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index 2b70da8f..54c7f1a8 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -24,8 +24,8 @@ class RequestCategoryFragment extends StatelessWidget { final user = Provider.of(context, listen: false).user; final settingProvider = Provider.of(context, listen: false); - // Check if user is FMS Engineer using SettingProvider - final bool isFMSEngineer = user?.type == UsersTypes.engineer && settingProvider.isUserFMS; + // Check if user is houseKeeper + final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ //TODO uncommit this to get the calendar card.. @@ -53,7 +53,7 @@ class RequestCategoryFragment extends StatelessWidget { : const SizedBox(), ], ).paddingOnly(start: 14, end: 12) - : isFMSEngineer + : isHouseKeeper ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/models/all_requests_and_count_model.dart b/lib/models/all_requests_and_count_model.dart index 09bfc03f..ba261802 100644 --- a/lib/models/all_requests_and_count_model.dart +++ b/lib/models/all_requests_and_count_model.dart @@ -134,36 +134,47 @@ class RequestsDetails { int? transactionType; int? numberOfAssets; int? numberOfSites; + String? siteName; + String? buildingName; + String? departmentName; + String? floorName; + String? roomName; - RequestsDetails( - {this.id, - this.nameOfType, - this.priority, - this.status, - this.statusValue, - this.assetName, - this.assetNo, - this.assetSN, - this.model, - this.supplier, - this.manufacturer, - this.requestType, - this.requestNo, - this.numberOfAssets, - this.numberOfSites, - this.sessionType, - this.gasType, - this.site, - this.statusReceiver, - this.assetTransferFrom, - this.assetTransferTo, - this.code, - this.siteTransferFrom, - this.siteTransferTo, - this.poInternal, - this.poExternal, - this.date, - this.transactionType}); + RequestsDetails({ + this.id, + this.nameOfType, + this.priority, + this.status, + this.statusValue, + this.assetName, + this.assetNo, + this.assetSN, + this.model, + this.supplier, + this.manufacturer, + this.requestType, + this.requestNo, + this.numberOfAssets, + this.numberOfSites, + this.sessionType, + this.gasType, + this.site, + this.statusReceiver, + this.assetTransferFrom, + this.assetTransferTo, + this.code, + this.siteTransferFrom, + this.siteTransferTo, + this.poInternal, + this.poExternal, + this.date, + this.transactionType, + this.siteName, + this.buildingName, + this.departmentName, + this.floorName, + this.roomName, + }); RequestsDetails.fromJson(Map json) { id = json['id']; @@ -194,6 +205,11 @@ class RequestsDetails { siteTransferFrom = json['siteTransferFrom']; siteTransferTo = json['siteTransferTo']; transactionType = json['transactionType']; + siteName = json['siteName']; + buildingName = json['buildingName']; + departmentName = json['departmentName']; + floorName = json['floorName']; + roomName = json['roomName']; } Map toJson() { @@ -224,6 +240,11 @@ class RequestsDetails { data['siteTransferFrom'] = siteTransferFrom; data['siteTransferTo'] = siteTransferTo; data['transactionType'] = transactionType; + data['siteName'] = siteName; + data['buildingName'] = buildingName; + data['departmentName'] = departmentName; + data['floorName'] = floorName; + data['roomName'] = roomName; return data; } } diff --git a/lib/models/enums/user_types.dart b/lib/models/enums/user_types.dart index 13238d83..c807669f 100644 --- a/lib/models/enums/user_types.dart +++ b/lib/models/enums/user_types.dart @@ -5,5 +5,6 @@ enum UsersTypes { assessor, assessorTl, qualityUser, - commissioningEngineer + commissioningEngineer, + houseKeeper } diff --git a/lib/models/new_models/dashboard_count.dart b/lib/models/new_models/dashboard_count.dart index 55bafffa..5c6fbcf8 100644 --- a/lib/models/new_models/dashboard_count.dart +++ b/lib/models/new_models/dashboard_count.dart @@ -37,9 +37,10 @@ class Data { int? countComplete; int? countAcknowledge; int? countHighPriority; + int? countClose; int? countOverdue; - Data({this.countOpen, this.countInprogress, this.countComplete, this.countAcknowledge, this.countHighPriority, this.countOverdue}); + Data({this.countOpen, this.countInprogress, this.countClose,this.countComplete, this.countAcknowledge, this.countHighPriority, this.countOverdue}); Data.fromJson(Map json) { countOpen = json['countOpen'] ?? 0; @@ -47,6 +48,7 @@ class Data { countComplete = json['countComplete'] ?? 0; countAcknowledge = json['countAcknowledge'] ?? 0; countHighPriority = json['countHighPriority'] ?? 0; + countClose = json['countClose'] ?? 0; countOverdue = json['countOverdue'] ?? 0; } @@ -57,6 +59,7 @@ class Data { data['countComplete'] = countComplete; data['countAcknowledge'] = countAcknowledge; data['countHighPriority'] = countHighPriority; + data['countClose'] = countClose; data['countOverdue'] = countOverdue; return data; } diff --git a/lib/models/new_models/dashboard_detail.dart b/lib/models/new_models/dashboard_detail.dart index d351337f..9c00cbbb 100644 --- a/lib/models/new_models/dashboard_detail.dart +++ b/lib/models/new_models/dashboard_detail.dart @@ -63,6 +63,11 @@ class Data { int? numberOfSites; int? numberOfAssets; String? nameOfType; + String? siteName; + String? buildingName; + String? floorName; + String? departmentName; + String? roomName; Data( {this.id, @@ -80,10 +85,15 @@ class Data { this.assetNumber, this.requestTypeName, this.requestNo, - this.requestType, - this.poExternal, - this.poInternal, - this.transactionNo}); + this.requestType, + this.poExternal, + this.poInternal, + this.transactionNo, + this.siteName, + this.departmentName, + this.buildingName, + this.floorName, + this.roomName}); Data.fromJson(Map json) { id = json['id']; @@ -106,6 +116,11 @@ class Data { poInternal = json['poInternal']; poExternal = json['poExternal']; nameOfType = json['nameOfType']; + siteName = json['siteName']; + departmentName = json['departmentName']; + buildingName = json['buildingName']; + floorName = json['floorName']; + roomName = json['roomName']; } Map toJson() { @@ -127,6 +142,11 @@ class Data { data['numberOfAssets'] = numberOfAssets; data['numberOfSites'] = numberOfSites; data['nameOfType'] = nameOfType; + data['siteName'] = siteName; + data['buildingName'] = buildingName; + data['departmentName'] = departmentName; + data['floorName'] = floorName; + data['roomName'] = roomName; return data; } } diff --git a/lib/models/user.dart b/lib/models/user.dart index 17da87c9..9bca631d 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -117,6 +117,8 @@ class User { return UsersTypes.qualityUser; case "R-201": // Quality User return UsersTypes.commissioningEngineer; + case "R-204": // Quality User + return UsersTypes.houseKeeper; default: return null; } diff --git a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart index 03e2ee38..6bbf5ef1 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart @@ -1,4 +1,5 @@ // UI: Cleaner Search Details View +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; @@ -18,6 +19,10 @@ import '../models/cleaner_task_search_result.dart'; import '../provider/cleaner_provider.dart'; import '../../pm_module/recurrent_wo/recurrent_work_order_view.dart'; import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:http/http.dart' as http; class CleanerSearchDetailsView extends StatefulWidget { static const String routeName = '/cleaner_search_details'; @@ -62,12 +67,11 @@ class _CleanerSearchDetailsViewState extends State { }); try { - final allRequestsProvider = Provider.of(context, listen: false); - await allRequestsProvider.getRecurrentWoById(id: widget.requestId); + // Use direct API call to get better error messages + final result = await _getRecurrentWoByIdWithMessage(widget.requestId); - // Map RecurrentWoData to CleanerTaskSearchResult - if (allRequestsProvider.recurrentWoData != null) { - _recurrentWoData = allRequestsProvider.recurrentWoData!; + if (result["success"] == true && result["data"] != null) { + _recurrentWoData = result["data"]; _isRequestOpen = _recurrentWoData?.status?.value == 4; _taskData = CleanerTaskSearchResult( id: _recurrentWoData!.id, @@ -80,16 +84,31 @@ class _CleanerSearchDetailsViewState extends State { locationDescription: null, createdDate: _recurrentWoData!.scheduleDate, ); + } else { + // Show exact backend error message + if (mounted) { + _showErrorDialog(context, result["message"] ?? "Failed to load request details. Please try again."); + } } } catch (e) { debugPrint('Error fetching recurrent work order: $e'); + if (mounted) { + _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); + } } finally { setState(() { _isLoading = false; }); } } else { - await _cleanerProvider.getCleanerRequestById(id: widget.requestId); + try { + await _cleanerProvider.getCleanerRequestById(id: widget.requestId); + } catch (e) { + debugPrint('Error fetching cleaner request: $e'); + if (mounted) { + _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); + } + } } } @@ -160,16 +179,32 @@ class _CleanerSearchDetailsViewState extends State { final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanQr())); if (result != null && result is String && result.isNotEmpty) { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - final isValid = await CleanerProvider().checkQRSameRoom(planRecurrentTaskId: widget.requestId, qrCode: result); - if (mounted) Navigator.pop(context); - if (isValid) { - if (mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), - ); + try { + final qrResult = await CleanerProvider().checkQRSameRoomWithMessage(planRecurrentTaskId: widget.requestId, qrCode: result); + if (mounted) Navigator.pop(context); - // getInitialData(); + final isValid = qrResult["success"] ?? false; + final backendMessage = qrResult["message"] ?? "Unknown error occurred"; + + if (isValid) { + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), + ); + } + } else { + if (mounted) { + // Show exact backend error message instead of our custom message + _showErrorDialog(context, backendMessage); + } + } + } catch (e) { + if (mounted) Navigator.pop(context); + if (mounted) { + // Extract actual error message from the exception or use generic fallback + String errorMessage = _extractErrorMessage(e) ?? 'Failed to validate QR code. Please try again.'; + _showErrorDialog(context, errorMessage); } } } @@ -293,4 +328,86 @@ class _CleanerSearchDetailsViewState extends State { ], ).toShadowContainer(context, padding: 0); } + + void _showErrorDialog(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: message, + title: 'Error', + onTap: () => Navigator.pop(context), + ), + ); + } + + String? _extractErrorMessage(dynamic error) { + // Implement your logic to extract error message from the exception + // For example, if the error is a custom exception with a message property: + if (error is ExceptionWithMessage) { + return error.message; + } else if (error is String) { + // If the error is a string, return it directly + return error; + } else if (error is Map) { + // If the error is a Map (e.g., parsed JSON), extract the message field + return error['message'] ?? error.toString(); + } + + // Fallback to a generic error message + return 'An unexpected error occurred. Please try again.'; + } + + /// Custom method to get recurrent work order with proper error message extraction + Future> _getRecurrentWoByIdWithMessage(int id) async { + try { + final response = await ApiManager.instance.get( + URLs.getRecurrentPlanByIdUrl + "?planRecurrentTaskId=$id", + enableToastMessage: false, // Disable automatic toast + ); + + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + final recurrentWoData = RecurrentWoData.fromJson(responseData["data"]); + + // Apply the same processing as in AllRequestsProvider + recurrentWoData.planRecurrentMedicalTaskRooms?.forEach((element) { + element.planRecurrentMedicalTaskRoomTabs?.forEach((tabs) { + tabs.planRecurrentMedicalTaskRoomTabAttributes?.forEach((attribute) { + if (attribute.attribute?.type == "bool" && attribute.attributeValue == null) { + attribute.attributeValue = "true"; + } + }); + }); + }); + + return { + "success": true, + "data": recurrentWoData, + "message": "Data loaded successfully", + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to load request details", + }; + } + } catch (error) { + debugPrint('Error in _getRecurrentWoByIdWithMessage: $error'); + return { + "success": false, + "data": null, + "message": "Failed to load request details. Please check your connection and try again.", + }; + } + } +} + +class ExceptionWithMessage implements Exception { + final String message; + + ExceptionWithMessage(this.message); } diff --git a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart index bb9fb739..2ed65884 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart @@ -21,6 +21,7 @@ import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/models/new_models/building.dart'; import 'package:test_sa/models/new_models/floor.dart'; import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; class CleanerSearchFormView extends StatefulWidget { static const String routeName = '/cleaner_search'; @@ -87,6 +88,7 @@ class _CleanerSearchFormViewState extends State { setState(() { _isLoadingSites = false; }); + _showErrorDialog(context, 'Failed to load sites. Please try again.'); } } @@ -106,6 +108,7 @@ class _CleanerSearchFormViewState extends State { _isLoadingBuildings = false; _buildingOptions = []; }); + _showErrorDialog(context, 'Failed to load buildings. Please try again.'); } } @@ -125,6 +128,7 @@ class _CleanerSearchFormViewState extends State { _isLoadingFloors = false; _floorOptions = []; }); + _showErrorDialog(context, 'Failed to load floors. Please try again.'); } } @@ -146,6 +150,7 @@ class _CleanerSearchFormViewState extends State { _locationDescriptionOptions = []; _allDeptRoomList = []; }); + _showErrorDialog(context, 'Failed to load location descriptions. Please try again.'); } } @@ -465,7 +470,7 @@ class _CleanerSearchFormViewState extends State { Future _onSubmit() async { // Validate that at least site is selected if (selectedSite == null) { - 'Please select a site'.showToast; + _showErrorDialog(context, 'Please select a site'); return; } @@ -502,11 +507,22 @@ class _CleanerSearchFormViewState extends State { }, ); } catch (e) { - 'Error searching'.showToast; + _showErrorDialog(context, 'Error searching. Please try again.'); } finally { setState(() { _isSearching = false; }); } } + + void _showErrorDialog(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: message, + title: 'Error', + onTap: () => Navigator.pop(context), + ), + ); + } } diff --git a/lib/modules/cleaner_module/provider/cleaner_provider.dart b/lib/modules/cleaner_module/provider/cleaner_provider.dart index a057aa54..ec6fff3f 100644 --- a/lib/modules/cleaner_module/provider/cleaner_provider.dart +++ b/lib/modules/cleaner_module/provider/cleaner_provider.dart @@ -145,6 +145,7 @@ class CleanerProvider extends ChangeNotifier { "planRecurrentTaskId": planRecurrentTaskId, "qrCode": qrCode, }, + showToast: false, // Disable automatic toast message ); if (response.statusCode >= 200 && response.statusCode < 300) { @@ -159,6 +160,49 @@ class CleanerProvider extends ChangeNotifier { } } + /// Check if QR code matches the same room for a plan recurrent task + /// Returns a map with success status and message from backend + Future> checkQRSameRoomWithMessage({ + required int planRecurrentTaskId, + required String qrCode, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.checkQRSameRoom, + body: { + "planRecurrentTaskId": planRecurrentTaskId, + "qrCode": qrCode, + }, + showToast: false, // Disable automatic toast message + ); + + // Parse the response body to extract the message + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + final isValid = responseData["data"] == true || responseData["isSuccess"] == true; + return { + "success": isValid, + "message": responseData["message"] ?? (isValid ? "QR code validated successfully" : "QR code validation failed"), + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "message": responseData["message"] ?? "QR code validation failed", + }; + } + } catch (error) { + debugPrint('Error checking QR same room: $error'); + return { + "success": false, + "message": "Failed to validate QR code. Please check your connection and try again.", + }; + } + } + void reset() { _isLoading = false; _searchResults = null; diff --git a/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart b/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart new file mode 100644 index 00000000..6abd4d04 --- /dev/null +++ b/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart @@ -0,0 +1,457 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/material.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/widget_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/models/ppm/recurrent_wo.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/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; +import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; + +class CleanerPreviewDialog extends StatelessWidget { + final RecurrentWoData model; + final double totalWorkingHours; + final VoidCallback onConfirm; + + const CleanerPreviewDialog({ + Key? key, + required this.model, + required this.totalWorkingHours, + required this.onConfirm, + }) : super(key: key); + + static void show( + BuildContext context, { + required RecurrentWoData model, + required double totalWorkingHours, + required VoidCallback onConfirm, + }) { + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => CleanerPreviewDialog( + model: model, + totalWorkingHours: totalWorkingHours, + onConfirm: onConfirm, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + child: Container( + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.85), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(14), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header matching DefaultAppBar style + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(14), + topRight: Radius.circular(14), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: 'Confirm Details'.heading3(context).custom( + fontWeight: FontWeight.w500, + ), + ), + Icon( + Icons.close, + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + size: 24, + ).onPress(() => Navigator.pop(context)), + ], + ), + ), + Divider(height: 1, color: context.isDark ? AppColor.neutral60 : AppColor.neutral100), + // Content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPreviewSection(context, 'Task Information', [ + _buildPreviewRow(context, context.translation.title, model.title ?? '-'), + _buildPreviewRow(context, context.translation.taskNo, model.taskNo ?? '-'), + _buildPreviewRow(context, context.translation.site, model.site?.siteName ?? '-'), + _buildPreviewRow(context, context.translation.department, model.department?.name ?? '-'), + _buildPreviewRow(context, context.translation.floor, model.floor?.name ?? '-'), + _buildPreviewRow(context, context.translation.room, model.room?.name ?? '-'), + _buildPreviewRow(context, context.translation.assignedEmployee, model.engineer?.userName ?? '-'), + ]), + 16.height, + // Total Working Time Section - Using WorkingTimeTile design + _buildPreviewSection(context, 'Total Working Time', [ + _buildWorkingTimeTile(context), + ]), + 16.height, + if (model.planRecurrentTaskCheckLists != null && model.planRecurrentTaskCheckLists!.isNotEmpty) ...[ + _buildPreviewSection(context, 'Checklist', [ + ...model.planRecurrentTaskCheckLists! + .map( + (item) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + item.isChecked ? Icons.check_circle : Icons.circle_outlined, + color: item.isChecked ? AppColor.primary10 : AppColor.neutral120, + size: 20, + ), + 8.width, + RichText( + text: TextSpan( + children: [ + TextSpan( + text: item.title, + style: AppTextStyles.bodyText2.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (item.isMandatory) + TextSpan( + text: ' *', + style: AppTextStyles.bodyText2.copyWith( + color: Colors.red, + ), + ), + ], + ), + ).expanded, + ], + ), + // Show comment if item is checked and has a comment + if (item.isChecked && item.comment.isNotEmpty == true) ...[ + 8.height, + Container( + width: double.infinity, + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(left: 28), + decoration: BoxDecoration( + color: context.isDark ? AppColor.neutral60.withValues(alpha: 0.3) : AppColor.neutral100.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Comment:'.bodyText2(context).custom( + fontWeight: FontWeight.w600, + color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, + ), + 4.height, + item.comment.bodyText2(context).custom( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ], + ), + ), + ], + ], + ), + ), + ) + .toList(), + ]), + 16.height, + ], + if (model.comment != null && model.comment!.isNotEmpty) ...[ + _buildPreviewSection(context, context.translation.comment, [ + model.comment!.bodyText(context), + ]), + 16.height, + ], + if (model.imageRoom != null && model.imageRoom!.isNotEmpty) ...[ + _buildPreviewSection(context, 'Captured Image', [ + _buildImagePreview(context, model.imageRoom!), + ]), + 16.height, + ], + ], + ), + ), + ), + // Footer with Confirm button + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + border: Border(top: BorderSide(color: context.isDark ? AppColor.neutral60 : AppColor.neutral100)), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(14), + bottomRight: Radius.circular(14), + ), + ), + child: AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: () { + Navigator.pop(context); + onConfirm(); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildPreviewSection(BuildContext context, String title, List children) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.heading6(context).custom(fontWeight: FontWeight.w600), + 8.height, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ).toShadowContainer(context, padding: 12), + ], + ); + } + + Widget _buildPreviewRow(BuildContext context, String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: '$label:'.bodyText2(context).custom(color: AppColor.neutral120), + ), + 8.width, + Expanded( + flex: 3, + child: value.bodyText2(context).custom(color: AppColor.neutral120), + ), + ], + ), + ); + } + + Widget _buildWorkingTimeTile(BuildContext context) { + // Create timer list from model data, same as in RecurrentTaskInfoWidget + final timerList = model.planRecurrentTaskTimers?.map((e) { + return TimerHistoryModel( + id: e.id, + startTime: e.startTime, + endTime: e.endTime, + workingHours: e.workingHours, + ); + }).toList() ?? []; + + return Container( + height: 56.toScreenHeight, + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + alignment: Alignment.centerLeft, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Total Working Time", + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: context.isDark ? null : AppColor.neutral20, + fontWeight: FontWeight.w500, + ), + ), + Text( + " ${CMRequestUtils.formatTotalWorkingHours(totalWorkingHours)}", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ).expanded, + 8.width, + const Icon(Icons.info_outline_rounded, color: AppColor.primary10), + ], + ), + ).onPress(() async { + await showModalBottomSheet( + context: context, + isDismissible: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + clipBehavior: Clip.antiAliasWithSaveLayer, + builder: (BuildContext context) => TotalWorkingTimeDetailBottomSheet(timerList: timerList), + ); + }); + } + + Widget _buildImagePreview(BuildContext context, String imageString) { + Widget imageWidget; + + // First check if it's base64 encoded (contains | separator) + if (imageString.contains('|')) { + // Format: "filename|base64data" + try { + final base64Data = imageString.split('|').last; + imageWidget = Image.memory(base64Decode(base64Data), fit: BoxFit.cover); + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget, + ).onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }); + } catch (e) { + debugPrint('Error decoding base64 image: $e'); + return _buildCameraIcon(context); + } + } + + // Check if it's a local file path + if (_isLocalUrl(imageString)) { + try { + imageWidget = Image.file(File(imageString), fit: BoxFit.cover); + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget, + ).onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }); + } catch (e) { + debugPrint('Error loading local file: $e'); + return _buildCameraIcon(context); + } + } + + // If not local and not base64, it's a server filename - use NetworkImage with loading indicator + final imageUrl = URLs.getFileUrl(imageString); + if (imageUrl != null) { + imageWidget = Image.network( + imageUrl, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Center( + child: CircularProgressIndicator( + color: AppColor.primary10, + value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + debugPrint('Error loading network image: $error'); + return _buildCameraIcon(context); + }, + ); + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget, + ).onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + )); + }); + } + + return _buildCameraIcon(context); + } + + /// Build camera icon widget for fallback + Widget _buildCameraIcon(BuildContext context) { + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.camera_alt, + size: 48, + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + ); + } + + /// Check if the URL is a local file path + bool _isLocalUrl(String url) { + return url.startsWith('/') || url.startsWith('file://') || (url.length > 1 && url[1] == ':'); + } +} diff --git a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart index d49be1bc..9446e975 100644 --- a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart +++ b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart @@ -7,7 +7,6 @@ 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/ppm/recurrent_wo.dart'; -import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/new_views/common_widgets/working_time_tile.dart'; @@ -18,10 +17,11 @@ import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart class RecurrentTaskInfoWidget extends StatefulWidget { final RecurrentWoData? model; - bool isClosed; - bool isHouseKeeper; + final bool isClosed; + final bool isHouseKeeper; + final DateTime? timerStartTime; - RecurrentTaskInfoWidget({super.key, required this.model, this.isClosed = false, this.isHouseKeeper = false}); + const RecurrentTaskInfoWidget({super.key, required this.model, this.isClosed = false, this.isHouseKeeper = false, this.timerStartTime}); @override State createState() => _RecurrentTaskInfoWidgetState(); @@ -87,7 +87,7 @@ class _RecurrentTaskInfoWidgetState extends State { '${context.translation.floor}: ${widget.model!.floor?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), '${context.translation.room}: ${widget.model!.room?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), ], - '${context.translation.assignedEmployee}: ${widget.model!.engineer!.userName ?? ""}'.bodyText2(context).custom(color: AppColor.neutral120), + '${context.translation.assignedEmployee}: ${widget.model!.engineer?.userName ?? ""}'.bodyText2(context).custom(color: AppColor.neutral120), '${context.translation.scheduledDate}: ${widget.model!.scheduleDate!.toMonthYearFormat}'.bodyText2(context).custom(color: AppColor.neutral120), ], ).toShadowContainer(context), @@ -122,6 +122,27 @@ class _RecurrentTaskInfoWidgetState extends State { } Widget _timerWidget(BuildContext context, double totalWorkingHours) { + // For housekeeper: show read-only timer counter with history + if (widget.isHouseKeeper) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!widget.isClosed) ...[ + _HousekeeperTimerWidget( + startTime: widget.timerStartTime, + isClosed: widget.isClosed, + ), + 12.height, + ], + if (totalWorkingHours > 0.0) + WorkingTimeTile( + timerList: timerList, + totalWorkingTime: totalWorkingHours, + ), + ], + ); + } + return Consumer(builder: (context, snapshot, child) { return Column( mainAxisSize: MainAxisSize.min, @@ -178,6 +199,92 @@ class _RecurrentTaskInfoWidgetState extends State { } } +// Read-only timer widget for housekeeper +class _HousekeeperTimerWidget extends StatefulWidget { + final DateTime? startTime; + final bool isClosed; + + const _HousekeeperTimerWidget({this.startTime, this.isClosed = false}); + + @override + State<_HousekeeperTimerWidget> createState() => _HousekeeperTimerWidgetState(); +} + +class _HousekeeperTimerWidgetState extends State<_HousekeeperTimerWidget> { + String _elapsedTime = "0:00:00"; + + @override + void initState() { + super.initState(); + if (widget.startTime != null && !widget.isClosed) { + _startTimer(); + } else if (widget.startTime != null && widget.isClosed) { + // If closed, calculate the final time + _updateElapsedTime(); + } + } + + void _updateElapsedTime() { + if (widget.startTime != null) { + final duration = DateTime.now().difference(widget.startTime!); + final hours = duration.inHours; + final minutes = duration.inMinutes % 60; + final seconds = duration.inSeconds % 60; + _elapsedTime = "$hours:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}"; + } + } + + void _startTimer() { + Future.delayed(const Duration(seconds: 1), () { + if (mounted && widget.startTime != null && !widget.isClosed) { + setState(() { + _updateElapsedTime(); + }); + _startTimer(); + } + }); + } + + @override + Widget build(BuildContext context) { + if (widget.startTime == null && !widget.isClosed) { + return SizedBox.shrink(); + } + + return Container( + height: 56.toScreenHeight, + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.05), + // blurRadius: 10, + // ) + // ], + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + context.translation.timer.tinyFont(context), + _elapsedTime.bodyText(context).custom( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ], + ), + ), + // No play/pause icon for housekeeper - timer runs automatically + ], + ), + ); + } +} + Widget buildingInfoWidget({required String label, required String? value, required BuildContext context}) { return Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart index 3b1a0a30..b350d626 100644 --- a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart +++ b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart @@ -1,7 +1,4 @@ -import 'dart:developer'; - 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'; @@ -10,6 +7,7 @@ import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/ppm/recurrent_wo.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/cleaner_module/widgets/cleaner_checklist_widget.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/cleaner_preview_dialog.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'; @@ -17,6 +15,7 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/pages/land_page/land_page.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import '../../../../../../controllers/providers/api/all_requests_provider.dart'; import 'components/room_tabs_widget.dart'; @@ -36,12 +35,21 @@ class RecurrentWorkOrderView extends StatefulWidget { class _RecurrentWorkOrderViewState extends State { AllRequestsProvider? allRequestsProvider; + DateTime? _housekeeperTimerStart; @override void initState() { allRequestsProvider = Provider.of(context, listen: false); - // calculateWorkingTime(); - Provider.of(context, listen: false).getRecurrentWoById(id: widget.taskId!); + Provider.of(context, listen: false).getRecurrentWoById(id: widget.taskId!).then((_) { + // Start timer automatically for housekeeper type + final provider = Provider.of(context, listen: false); + bool isHouseKeeper = provider.recurrentWoData?.planRecurrentTaskCheckLists != null && + provider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty; + + if (isHouseKeeper && provider.recurrentWoData?.status?.value != 1) { + _housekeeperTimerStart = DateTime.now(); + } + }); super.initState(); } @@ -63,7 +71,7 @@ class _RecurrentWorkOrderViewState extends State { onWillPopScope: isClosed ? null : () { - _updateTask(context: context, status: 0); + _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper); }, ), body: allRequestsProvider!.isLoading @@ -78,7 +86,12 @@ class _RecurrentWorkOrderViewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - RecurrentTaskInfoWidget(isClosed: isClosed, model: requestProvider.recurrentWoData, isHouseKeeper: isHouseKeeper), + RecurrentTaskInfoWidget( + isClosed: isClosed, + model: requestProvider.recurrentWoData, + isHouseKeeper: isHouseKeeper, + timerStartTime: _housekeeperTimerStart, + ), IgnorePointer( ignoring: isClosed, child: requestProvider.recurrentWoData!.planRecurrentMedicalTaskRooms != null && requestProvider.recurrentWoData!.planRecurrentMedicalTaskRooms!.isNotEmpty @@ -121,14 +134,13 @@ class _RecurrentWorkOrderViewState extends State { label: context.translation.save, buttonColor: context.isDark ? AppColor.neutral20 : AppColor.white60, textColor: context.isDark ? Colors.white : AppColor.black10, - onPressed: () => _updateTask(context: context, status: 0), + onPressed: () => _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper), ).expanded, 12.width, AppFilledButton( label: context.translation.complete, buttonColor: AppColor.primary10, - // textColor: context.isDark?Colors.white: AppColor.black10, - onPressed: () => _updateTask(context: context, status: 1), + onPressed: () => _updateTask(context: context, status: 1, isHouseKeeper: isHouseKeeper), ).expanded, ], ), @@ -142,91 +154,176 @@ class _RecurrentWorkOrderViewState extends State { cxt: context, showPopUp: !isClosed, onSave: () { - _updateTask(context: context, status: 0); + _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper); }); }); } -} -void _updateTask({required BuildContext context, required int status}) async { - AllRequestsProvider allRequestsProvider = Provider.of(context, listen: false); + void _updateTask({required BuildContext context, required int status, required bool isHouseKeeper}) async { + AllRequestsProvider allRequestsProvider = Provider.of(context, listen: false); - // Only validate checklist if it exists and status is complete - if (status == 1 && allRequestsProvider.recurrentWoData?.planRecurrentTaskCheckLists != null && allRequestsProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty) { - // Validate mandatory checklist items only - if (!_validateChecklist(allRequestsProvider.recurrentWoData!)) { + // Only validate checklist if it exists and status is complete + if (status == 1 && allRequestsProvider.recurrentWoData?.planRecurrentTaskCheckLists != null && allRequestsProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty) { + // Validate mandatory checklist items only + if (!_validateChecklist(context, allRequestsProvider.recurrentWoData!)) { + return; + } + } + + // For housekeeper: show preview dialog before completing + if (isHouseKeeper && status == 1) { + _showPreviewDialog(context, allRequestsProvider); return; } - // Image validation is handled by backend - no need to force capture here + // Proceed with update + _performUpdate(context: context, status: status, isHouseKeeper: isHouseKeeper); } - if (validate(model: allRequestsProvider.recurrentWoData!, status: status)) { - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; - - if (allRequestsProvider.recurrentWoData?.recurrentWoTimePicker != null) { - int durationInSecond = allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt!.difference(allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!).inSeconds; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( - PlanRecurrentTaskTimers( - id: 0, - startTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!.toIso8601String(), - endTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt?.toIso8601String(), - workingHours: ((durationInSecond) / 60 / 60), - ), - ); - } - allRequestsProvider.recurrentWoData?.timerModelList?.forEach((timer) { - int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( - PlanRecurrentTaskTimers( - id: 0, - startTime: timer.startAt!.toIso8601String(), - endTime: timer.endAt?.toIso8601String(), - workingHours: ((durationInSecond) / 60 / 60), - ), - ); - }); + void _performUpdate({required BuildContext context, required int status, required bool isHouseKeeper}) async { + AllRequestsProvider allRequestsProvider = Provider.of(context, listen: false); + + if (validate(context: context, model: allRequestsProvider.recurrentWoData!, status: status, isHouseKeeper: isHouseKeeper)) { + // For housekeeper: automatically add timer data + if (isHouseKeeper && _housekeeperTimerStart != null) { + DateTime endTime = DateTime.now(); + int durationInSecond = endTime.difference(_housekeeperTimerStart!).inSeconds; - final uniqueTimersMap = {}; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.forEach((timer) { - if (timer.startTime != null && timer.endTime != null) { - final key = '${timer.id}_${timer.startTime}_${timer.endTime}'; - uniqueTimersMap[key] = timer; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( + PlanRecurrentTaskTimers( + id: 0, + startTime: _housekeeperTimerStart!.toIso8601String(), + endTime: endTime.toIso8601String(), + workingHours: ((durationInSecond) / 60 / 60), + ), + ); + } else { + // For non-housekeeper: use existing timer logic + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; + + if (allRequestsProvider.recurrentWoData?.recurrentWoTimePicker != null) { + int durationInSecond = allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt!.difference(allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!).inSeconds; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( + PlanRecurrentTaskTimers( + id: 0, + startTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!.toIso8601String(), + endTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt?.toIso8601String(), + workingHours: ((durationInSecond) / 60 / 60), + ), + ); + } + allRequestsProvider.recurrentWoData?.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( + PlanRecurrentTaskTimers( + id: 0, + startTime: timer.startAt!.toIso8601String(), + endTime: timer.endAt?.toIso8601String(), + workingHours: ((durationInSecond) / 60 / 60), + ), + ); + }); } - }); - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = uniqueTimersMap.values.toList(); - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + final uniqueTimersMap = {}; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.forEach((timer) { + if (timer.startTime != null && timer.endTime != null) { + final key = '${timer.id}_${timer.startTime}_${timer.endTime}'; + uniqueTimersMap[key] = timer; + } + }); + + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = uniqueTimersMap.values.toList(); + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - await allRequestsProvider.updateRecurrentWo(status: status).then((success) { - Navigator.pop(context); // Close loading dialog - if (success) { - if (status == 1) { - // When complete is clicked, navigate to LandPage (Dashboard) - allRequestsProvider.reset(); - allRequestsProvider.getAllRequests(context, typeTransaction: 5); - allRequestsProvider.recurrentWoData?.recurrentWoTimerModel = null; + await allRequestsProvider.updateRecurrentWo(status: status, showToast: false).then((result) { + Navigator.pop(context); // Close loading dialog - // Navigate to LandPage and clear all previous routes - Navigator.of(context).pushNamedAndRemoveUntil(LandPage.routeName, (routes) => false); + bool success = result['success'] ?? false; + String message = result['message'] ?? ''; + + if (success) { + if (status == 1) { + // When complete is clicked, navigate to LandPage (Dashboard) + allRequestsProvider.reset(); + allRequestsProvider.getAllRequests(context, typeTransaction: 5); + allRequestsProvider.recurrentWoData?.recurrentWoTimerModel = null; + + // Navigate to LandPage and clear all previous routes + Navigator.of(context).pushNamedAndRemoveUntil(LandPage.routeName, (routes) => false); + } else { + // For save (status 0), just pop the current screen + allRequestsProvider.recurrentWoData?.recurrentWoTimerModel = null; + Navigator.pop(context); + } } else { - // For save (status 0), just pop the current screen - allRequestsProvider.recurrentWoData?.recurrentWoTimerModel = null; - Navigator.pop(context); + // Show error dialog with the exact message from API + _showErrorDialog(context, message.isNotEmpty ? message : "Failed to update the task. Please try again."); } + }).catchError((error) { + Navigator.pop(context); // Close loading dialog + _showErrorDialog(context, error.toString()); + }); + } + } + + void _showPreviewDialog(BuildContext context, AllRequestsProvider provider) { + final model = provider.recurrentWoData!; + + // Calculate total working time + double totalWorkingHours = 0; + final timers = model.planRecurrentTaskTimers ?? []; + totalWorkingHours = timers.fold(0.0, (sum, item) { + if (item.startTime == null || item.endTime == null) return sum; + try { + final start = DateTime.parse(item.startTime!); + final end = DateTime.parse(item.endTime!); + final diffInHours = end.difference(start).inSeconds / 3600.0; + return sum + diffInHours; + } catch (_) { + return sum; } }); + + // Add current timer if running + if (_housekeeperTimerStart != null) { + final currentDuration = DateTime.now().difference(_housekeeperTimerStart!).inSeconds / 3600.0; + totalWorkingHours += currentDuration; + } + + CleanerPreviewDialog.show( + context, + model: model, + totalWorkingHours: totalWorkingHours, + onConfirm: () => _performUpdate(context: context, status: 1, isHouseKeeper: true), + ); + } + + void _showErrorDialog(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: message, + title: 'Error', + onTap: () => Navigator.pop(context), + ), + ); } } -bool _validateChecklist(RecurrentWoData model) { +bool _validateChecklist(BuildContext context, RecurrentWoData model) { // Check if all mandatory checklist items are checked final uncheckedMandatory = model.planRecurrentTaskCheckLists!.where((item) => item.isMandatory && !item.isChecked).toList(); if (uncheckedMandatory.isNotEmpty) { - Fluttertoast.showToast( - msg: "Please complete all mandatory tasks (marked with *)", - toastLength: Toast.LENGTH_LONG, + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Please complete all mandatory tasks (marked with *)", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), ); return false; } @@ -234,31 +331,72 @@ bool _validateChecklist(RecurrentWoData model) { return true; } -bool validate({required RecurrentWoData model, required int status}) { +bool validate({required BuildContext context, required RecurrentWoData model, required int status, required bool isHouseKeeper}) { + // Skip timer validation for housekeeper type + if (isHouseKeeper) { + return true; + } + + // Original timer validation logic for non-housekeeper types bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; if (isTimerPickerEnable) { if (model.recurrentWoTimerModel?.startAt == null && model.recurrentWoTimePicker == null) { - Fluttertoast.showToast(msg: "Working Hours Required"); + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Working Hours Required", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), + ); return false; } if (model.recurrentWoTimePicker == null) { if (model.recurrentWoTimerModel?.startAt == null) { - Fluttertoast.showToast(msg: "Working Hours Required"); + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Working Hours Required", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), + ); return false; } if (model.recurrentWoTimerModel?.endAt == null) { - Fluttertoast.showToast(msg: "Please Stop The Timer"); + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Please Stop The Timer", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), + ); return false; } } } else { if (model.recurrentWoTimerModel?.startAt == null) { - Fluttertoast.showToast(msg: "Working Hours Required"); + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Working Hours Required", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), + ); return false; } if (model.recurrentWoTimerModel?.endAt == null) { - Fluttertoast.showToast(msg: "Please Stop The Timer"); + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: "Please Stop The Timer", + title: 'Validation Error', + onTap: () => Navigator.pop(context), + ), + ); return false; } } 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 9b5a7441..f1e35b4c 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 @@ -127,8 +127,11 @@ class _AllRequestsFilterPageState extends State { if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { types['Incidents'] = 16; } - - final statuses = { +//No need to add check for assetgroup type as this role will only for FMS + if (context.userProvider.isHouseKeeper) { + types = {context.translation.recurrentWo: 5}; + } + var statuses = { "All WO": 0, context.translation.open: 1, context.translation.inProgress: 2, @@ -136,6 +139,16 @@ class _AllRequestsFilterPageState extends State { context.translation.closed: 3, "Canceled": 5, }; + if (context.userProvider.isHouseKeeper) { + statuses = { + "All WO": 0, + // context.translation.open: 1, + context.translation.inProgress: 2, + // context.translation.completed: 4, + context.translation.closed: 3, + // "Canceled": 5, + }; + } return Scaffold( appBar: DefaultAppBar( title: context.translation.filter, 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 ba49458e..123f4ad0 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 @@ -106,6 +106,14 @@ class _MyRequestsPageState extends State { requestsList.add(Request(16, "Incidents")); } + if (context.userProvider.isHouseKeeper) { + requestsList = [ + Request(null, context.translation.allWorkOrder), + Request(5, context.translation.recurrentWo), + // Request(15, 'Demo'), moved to permission api + ]; + } + _provider = Provider.of(context, listen: false); _provider!.reset(); WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart index 0f63745e..bf3db783 100644 --- a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart +++ b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart @@ -27,7 +27,7 @@ class RecurrentWoItemView extends StatelessWidget { Widget build(BuildContext context) { final user = Provider.of(context, listen: false).user; final settingProvider = Provider.of(context, listen: false); - final bool isFMSEngineer = user?.type == UsersTypes.engineer && settingProvider.isUserFMS; + final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; if (requestData != null) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -55,12 +55,22 @@ class RecurrentWoItemView extends StatelessWidget { ], ), 8.height, - (requestData?.typeTransaction ?? context.translation.ppmRequest).heading5(context), + (requestData?.typeTransaction ?? context.translation.recurrentWo).heading5(context), 8.height, // '${context.translation.assetNumber}: ${requestData!.assetNumber}'.bodyText(context), // '${context.translation.assetSN}: ${request.assetSN}'.bodyText(context),//todo ask ahmed // '${context.translation.code}: ${request.code}'.bodyText(context), '${context.translation.taskNo}: ${requestData!.requestNo}'.bodyText(context), + if(requestData?.siteName!=null) + '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), + if(requestData!.buildingName!=null) + '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), + if(requestData!.departmentName!=null) + '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), + if(requestData!.floorName!=null) + '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), + if(requestData!.roomName!=null) + '${context.translation.room}: ${requestData!.roomName}'.bodyText(context), 8.height, Row( mainAxisSize: MainAxisSize.min, @@ -75,7 +85,7 @@ class RecurrentWoItemView extends StatelessWidget { ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() { - if (isFMSEngineer && (requestData?.statusValue == 4)) { + if (isHouseKeeper && (requestData?.statusValue == 4)) { Navigator.push( context, MaterialPageRoute( @@ -123,6 +133,11 @@ class RecurrentWoItemView extends StatelessWidget { '${context.translation.site}: ${requestDetails!.site}'.bodyText(context), // '${context.translation.code}: ${request.code}'.bodyText(context), '${context.translation.taskNo}: ${requestDetails!.requestNo}'.bodyText(context), + if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), + if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), + if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), + if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), + if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(context), 8.height, Row( mainAxisSize: MainAxisSize.min, @@ -137,7 +152,7 @@ class RecurrentWoItemView extends StatelessWidget { ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { - if (isFMSEngineer && (requestDetails?.statusValue == 4)) { + if (isHouseKeeper && (requestDetails?.statusValue == 4)) { Navigator.push( context, MaterialPageRoute( diff --git a/lib/views/widgets/dialogs/confirm_dialog.dart b/lib/views/widgets/dialogs/confirm_dialog.dart index 83b05faa..6d8ef764 100644 --- a/lib/views/widgets/dialogs/confirm_dialog.dart +++ b/lib/views/widgets/dialogs/confirm_dialog.dart @@ -44,6 +44,7 @@ class ConfirmDialog extends StatelessWidget { ) ], ), + 16.height, message.bodyText(context), 28.height, AppFilledButton( From 05960f28e059ad962f1bb9a2ae9b9761adedd6b7 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 5 May 2026 15:57:50 +0300 Subject: [PATCH 06/73] house keeper should not create any request. --- lib/new_views/common_widgets/app_bottom_nav_bar.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/new_views/common_widgets/app_bottom_nav_bar.dart b/lib/new_views/common_widgets/app_bottom_nav_bar.dart index 4195d6e4..611afb27 100644 --- a/lib/new_views/common_widgets/app_bottom_nav_bar.dart +++ b/lib/new_views/common_widgets/app_bottom_nav_bar.dart @@ -19,11 +19,12 @@ class AppBottomNavigationBar extends StatelessWidget { bool isEngineer = (Provider.of(context, listen: false).user?.type) == UsersTypes.engineer; bool isUserFMS = Provider.of(context, listen: false).isUserFMS; bool commissioningEng = context.userProvider.isCommissioningEngineer; + bool isHouseKeeper = context.userProvider.isHouseKeeper; List navItems = [ NavItemModel(0, "overview", context.translation.overview), NavItemModel(1, "request_icon", context.translation.workOrder), - if (!commissioningEng) NavItemModel(2, "add_icon", context.translation.calendar, showLabel: false), + if (!commissioningEng && !isHouseKeeper) NavItemModel(2, "add_icon", context.translation.calendar, showLabel: false), NavItemModel(3, "assets", context.translation.assets), NavItemModel(4, "contact", context.translation.contact), ]; From 5c46fdd8e5e50d5ec6c1fca0040aba0ce438cbd2 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 21 May 2026 15:24:33 +0300 Subject: [PATCH 07/73] new flow api implemented --- lib/controllers/api_routes/urls.dart | 8 +- .../models/one_time_task_data.dart | 236 +++++++ .../models/qr_scan_location_data.dart | 60 ++ .../cleaner_module/models/task_type.dart | 26 + .../pages/cleaner_search_form_view.dart | 625 +++++++----------- .../pages/one_time_task_view.dart | 437 ++++++++++++ .../provider/cleaner_provider.dart | 179 +++++ .../provider/task_type_provider.dart | 74 +++ 8 files changed, 1243 insertions(+), 402 deletions(-) create mode 100644 lib/modules/cleaner_module/models/one_time_task_data.dart create mode 100644 lib/modules/cleaner_module/models/qr_scan_location_data.dart create mode 100644 lib/modules/cleaner_module/models/task_type.dart create mode 100644 lib/modules/cleaner_module/pages/one_time_task_view.dart create mode 100644 lib/modules/cleaner_module/provider/task_type_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 2ba006ab..e2b02926 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -71,8 +71,12 @@ class URLs { static get searchPlanRecurrentTaskListSearch => "$_baseUrl/PlanRecurrentTasks/PlanRecurrentTaskListSearchDto"; // get static get checkQRSameRoom => "$_baseUrl/PlanRecurrentTasks/CheckQRSameRoom"; // get - // - + //One Time Task Urls cleaner module new flow urls .... + static get getTaskType => "$_baseUrl/OneTimeTasks/GetTaskTypes"; + static get scanQRCode => "$_baseUrl/OneTimeTasks/ScanQRCode"; + static get proceedRequest => "$_baseUrl/OneTimeTasks/Proceed"; + static get completeRequest => "$_baseUrl/OneTimeTasks/Complete"; + static get getRequestById => "$_baseUrl/OneTimeTasks/GetById"; //service request new flow urls. static get nurseDashboardCountUrl => '$_baseUrl/ServiceRequest/GetDashboardNurseCount'; diff --git a/lib/modules/cleaner_module/models/one_time_task_data.dart b/lib/modules/cleaner_module/models/one_time_task_data.dart new file mode 100644 index 00000000..817ae685 --- /dev/null +++ b/lib/modules/cleaner_module/models/one_time_task_data.dart @@ -0,0 +1,236 @@ +class OneTimeTaskData { + int? id; + int? oneTimeSettingId; + OneTimeSetting? oneTimeSetting; + String? userId; + String? userName; + int? siteId; + String? siteName; + int? buildingId; + String? buildingName; + int? floorId; + String? floorName; + int? departmentID; + String? departmentName; + int? roomId; + String? roomName; + String? locationDescription; + String? startTime; + String? endTime; + int? statusId; + String? statusName; + int? statusValue; + List? instructions; + List? attachments; + String? createdDate; + String? createdBy; + String? imageRoom; // For captured image + + OneTimeTaskData({ + this.id, + this.oneTimeSettingId, + this.oneTimeSetting, + this.userId, + this.userName, + this.siteId, + this.siteName, + this.buildingId, + this.buildingName, + this.floorId, + this.floorName, + this.departmentID, + this.departmentName, + this.roomId, + this.roomName, + this.locationDescription, + this.startTime, + this.endTime, + this.statusId, + this.statusName, + this.statusValue, + this.instructions, + this.attachments, + this.createdDate, + this.createdBy, + this.imageRoom, + }); + + factory OneTimeTaskData.fromJson(Map json) { + return OneTimeTaskData( + id: json['id'], + oneTimeSettingId: json['oneTimeSettingId'], + oneTimeSetting: json['oneTimeSetting'] != null ? OneTimeSetting.fromJson(json['oneTimeSetting']) : null, + userId: json['userId'], + userName: json['userName'], + siteId: json['siteId'], + siteName: json['siteName'], + buildingId: json['buildingId'], + buildingName: json['buildingName'], + floorId: json['floorId'], + floorName: json['floorName'], + departmentID: json['departmentID'], + departmentName: json['departmentName'], + roomId: json['roomId'], + roomName: json['roomName'], + locationDescription: json['locationDescription'], + startTime: json['startTime'], + endTime: json['endTime'], + statusId: json['statusId'], + statusName: json['statusName'], + statusValue: json['statusValue'], + instructions: json['instructions'] != null + ? (json['instructions'] as List).map((i) => Instruction.fromJson(i)).toList() + : null, + attachments: json['attachments'] != null + ? (json['attachments'] as List).map((a) => Attachment.fromJson(a)).toList() + : null, + createdDate: json['createdDate'], + createdBy: json['createdBy'], + imageRoom: json['imageRoom'], + ); + } + + Map toJson() { + return { + 'id': id, + 'oneTimeSettingId': oneTimeSettingId, + 'oneTimeSetting': oneTimeSetting?.toJson(), + 'userId': userId, + 'userName': userName, + 'siteId': siteId, + 'siteName': siteName, + 'buildingId': buildingId, + 'buildingName': buildingName, + 'floorId': floorId, + 'floorName': floorName, + 'departmentID': departmentID, + 'departmentName': departmentName, + 'roomId': roomId, + 'roomName': roomName, + 'locationDescription': locationDescription, + 'startTime': startTime, + 'endTime': endTime, + 'statusId': statusId, + 'statusName': statusName, + 'statusValue': statusValue, + 'instructions': instructions?.map((i) => i.toJson()).toList(), + 'attachments': attachments?.map((a) => a.toJson()).toList(), + 'createdDate': createdDate, + 'createdBy': createdBy, + 'imageRoom': imageRoom, + }; + } +} + +class OneTimeSetting { + int? id; + int? taskTypeId; + String? taskTypeName; + int? instructionDescriptionId; + String? instructionDescriptionName; + int? sla; + + OneTimeSetting({ + this.id, + this.taskTypeId, + this.taskTypeName, + this.instructionDescriptionId, + this.instructionDescriptionName, + this.sla, + }); + + factory OneTimeSetting.fromJson(Map json) { + return OneTimeSetting( + id: json['id'], + taskTypeId: json['taskTypeId'], + taskTypeName: json['taskTypeName'], + instructionDescriptionId: json['instructionDescriptionId'], + instructionDescriptionName: json['instructionDescriptionName'], + sla: json['sla'], + ); + } + + Map toJson() { + return { + 'id': id, + 'taskTypeId': taskTypeId, + 'taskTypeName': taskTypeName, + 'instructionDescriptionId': instructionDescriptionId, + 'instructionDescriptionName': instructionDescriptionName, + 'sla': sla, + }; + } +} + +class Instruction { + int? id; + int? instructionTextId; + String? instructionText; + bool? isRequired; + bool? isDone; + String? comment; + + Instruction({ + this.id, + this.instructionTextId, + this.instructionText, + this.isRequired, + this.isDone, + this.comment, + }); + + factory Instruction.fromJson(Map json) { + return Instruction( + id: json['id'], + instructionTextId: json['instructionTextId'], + instructionText: json['instructionText'], + isRequired: json['isRequired'], + isDone: json['isDone'], + comment: json['comment'], + ); + } + + Map toJson() { + return { + 'id': id, + 'instructionTextId': instructionTextId, + 'instructionText': instructionText, + 'isRequired': isRequired, + 'isDone': isDone, + 'comment': comment, + }; + } +} + +class Attachment { + int? id; + String? fileName; + String? fileUrl; + String? fileType; + + Attachment({ + this.id, + this.fileName, + this.fileUrl, + this.fileType, + }); + + factory Attachment.fromJson(Map json) { + return Attachment( + id: json['id'], + fileName: json['fileName'], + fileUrl: json['fileUrl'], + fileType: json['fileType'], + ); + } + + Map toJson() { + return { + 'id': id, + 'fileName': fileName, + 'fileUrl': fileUrl, + 'fileType': fileType, + }; + } +} + diff --git a/lib/modules/cleaner_module/models/qr_scan_location_data.dart b/lib/modules/cleaner_module/models/qr_scan_location_data.dart new file mode 100644 index 00000000..a5b914d0 --- /dev/null +++ b/lib/modules/cleaner_module/models/qr_scan_location_data.dart @@ -0,0 +1,60 @@ +class QrScanLocationData { + int? siteId; + String? siteName; + int? buildingId; + String? buildingName; + int? floorId; + String? floorName; + int? departmentId; + String? departmentName; + int? roomId; + String? roomName; + String? locationDescription; + + QrScanLocationData({ + this.siteId, + this.siteName, + this.buildingId, + this.buildingName, + this.floorId, + this.floorName, + this.departmentId, + this.departmentName, + this.roomId, + this.roomName, + this.locationDescription, + }); + + factory QrScanLocationData.fromJson(Map json) { + return QrScanLocationData( + siteId: json['siteId'], + siteName: json['siteName'], + buildingId: json['buildingId'], + buildingName: json['buildingName'], + floorId: json['floorId'], + floorName: json['floorName'], + departmentId: json['departmentId'], + departmentName: json['departmentName'], + roomId: json['roomId'], + roomName: json['roomName'], + locationDescription: json['locationDescription'], + ); + } + + Map toJson() { + return { + 'siteId': siteId, + 'siteName': siteName, + 'buildingId': buildingId, + 'buildingName': buildingName, + 'floorId': floorId, + 'floorName': floorName, + 'departmentId': departmentId, + 'departmentName': departmentName, + 'roomId': roomId, + 'roomName': roomName, + 'locationDescription': locationDescription, + }; + } +} + diff --git a/lib/modules/cleaner_module/models/task_type.dart b/lib/modules/cleaner_module/models/task_type.dart new file mode 100644 index 00000000..a42b906b --- /dev/null +++ b/lib/modules/cleaner_module/models/task_type.dart @@ -0,0 +1,26 @@ +import 'package:test_sa/models/base.dart'; + +class TaskType extends Base { + int? id; + String? taskTypeName; + + TaskType({this.id, this.taskTypeName}) : super(identifier: id?.toString(), name: taskTypeName); + + @override + bool operator ==(Object other) => identical(this, other) || other is TaskType && id != null && id == other.id; + + @override + int get hashCode => id.hashCode; + + Map toJson() { + return {"id": id, "taskTypeName": taskTypeName}; + } + + factory TaskType.fromJson(Map? parsedJson) { + if (parsedJson == null) return TaskType(); + return TaskType( + id: parsedJson["id"], + taskTypeName: parsedJson["taskTypeName"], + ); + } +} diff --git a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart index 2ed65884..9cc1f1d8 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart @@ -1,27 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; -import 'package:test_sa/extensions/string_extensions.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/hospital.dart'; -import 'package:test_sa/models/lookup.dart'; -import 'package:test_sa/models/new_models/department_room_combined.dart'; -import 'package:test_sa/modules/cleaner_module/pages/cleaner_search_list_view.dart'; +import 'package:test_sa/modules/cleaner_module/models/task_type.dart'; +import 'package:test_sa/modules/cleaner_module/models/qr_scan_location_data.dart'; import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_building_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_floor_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_location_description_provider.dart'; +import 'package:test_sa/modules/cleaner_module/provider/task_type_provider.dart'; +import 'package:test_sa/modules/cleaner_module/pages/one_time_task_view.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; -import 'package:test_sa/models/new_models/building.dart'; -import 'package:test_sa/models/new_models/floor.dart'; -import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/views/widgets/qr/scan_qr.dart'; class CleanerSearchFormView extends StatefulWidget { static const String routeName = '/cleaner_search'; @@ -33,165 +27,140 @@ class CleanerSearchFormView extends StatefulWidget { } class _CleanerSearchFormViewState extends State { - Hospital? selectedSite; - Building? selectedBuilding; - Floor? selectedFloor; - DepartmentRoomCombined? selectedDepartmentRoom; - Lookup? selectedLocationDescription; - late FocusNode _siteFocusNode; - final TextEditingController _siteController = TextEditingController(); - - List _siteOptions = []; - List _buildingOptions = []; - List _floorOptions = []; - List _locationDescriptionOptions = []; - List _allDeptRoomList = []; - - bool _isLoadingSites = false; - bool _isLoadingBuildings = false; - bool _isLoadingFloors = false; - bool _isLoadingLocationDescriptions = false; - bool _isSearching = false; + TaskType? selectedTaskType; + List _taskTypeOptions = []; + bool _isLoadingTaskTypes = false; + bool _isScanning = false; + bool _isProceeding = false; + QrScanLocationData? _scannedLocationData; @override void initState() { super.initState(); - _siteFocusNode = FocusNode(); + _loadTaskTypes(); } @override void dispose() { - _siteController.dispose(); - _siteFocusNode.dispose(); super.dispose(); } - Future _searchSites(String query) async { - if (query.isEmpty) { - setState(() { - _siteOptions = []; - }); - return; - } - + Future _loadTaskTypes() async { setState(() { - _isLoadingSites = true; + _isLoadingTaskTypes = true; }); try { - final sites = await HospitalsProvider().getHospitalsListByVal(searchVal: query); + final taskTypes = await TaskTypeProvider().getTaskTypesList(); setState(() { - _siteOptions = sites; - _isLoadingSites = false; + _taskTypeOptions = taskTypes; + _isLoadingTaskTypes = false; }); } catch (e) { setState(() { - _isLoadingSites = false; + _isLoadingTaskTypes = false; + _taskTypeOptions = []; }); - _showErrorDialog(context, 'Failed to load sites. Please try again.'); + _showErrorDialog(context, 'Failed to load task types. Please try again.'); } } - Future _loadBuildings(int siteId) async { - setState(() { - _isLoadingBuildings = true; - }); + Future _onScanQRCode() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ScanQr()), + ); - try { - final buildings = await FmsBuildingProvider().getBuildingsListBySiteId(siteId); - setState(() { - _buildingOptions = buildings; - _isLoadingBuildings = false; - }); - } catch (e) { + if (result != null && result is String && result.isNotEmpty) { setState(() { - _isLoadingBuildings = false; - _buildingOptions = []; + _isScanning = true; }); - _showErrorDialog(context, 'Failed to load buildings. Please try again.'); + + try { + final qrResult = await CleanerProvider().scanQRCode(qrCode: result); + + final isSuccess = qrResult["success"] ?? false; + final backendMessage = qrResult["message"] ?? "Unknown error occurred"; + + if (isSuccess && qrResult["data"] != null) { + // Store the scanned location data + setState(() { + _scannedLocationData = QrScanLocationData.fromJson(qrResult["data"]); + _isScanning = false; + }); + } else { + setState(() { + _isScanning = false; + }); + if (mounted) { + _showErrorDialog(context, backendMessage); + } + } + } catch (e) { + setState(() { + _isScanning = false; + }); + if (mounted) { + _showErrorDialog(context, 'Failed to scan QR code. Please try again.'); + } + } } } - Future _loadFloors(int siteId, int buildingId) async { - setState(() { - _isLoadingFloors = true; - }); + Future _onProceed() async { + // Validate that task type and location data are available + if (selectedTaskType == null) { + _showErrorDialog(context, 'Please select a task type'); + return; + } - try { - final floors = await FmsFloorProvider().getFloorsListBySiteAndBuilding(siteId, buildingId); - setState(() { - _floorOptions = floors; - _isLoadingFloors = false; - }); - } catch (e) { - setState(() { - _isLoadingFloors = false; - _floorOptions = []; - }); - _showErrorDialog(context, 'Failed to load floors. Please try again.'); + if (_scannedLocationData == null) { + _showErrorDialog(context, 'Please scan a QR code first'); + return; } - } - Future _loadLocationDescriptions(int siteId, int buildingId, int floorId) async { setState(() { - _isLoadingLocationDescriptions = true; + _isProceeding = true; }); try { - final response = await FmsLocationDescriptionProvider().getLocationDescriptionsList(siteId, buildingId, floorId); - setState(() { - _locationDescriptionOptions = response?.locationDescriptions ?? []; - _allDeptRoomList = response?.deptRoom ?? []; - _isLoadingLocationDescriptions = false; - }); - } catch (e) { - setState(() { - _isLoadingLocationDescriptions = false; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - _showErrorDialog(context, 'Failed to load location descriptions. Please try again.'); - } - } - - List _getDepartmentRoomCombinations() { - if (selectedLocationDescription == null || _allDeptRoomList.isEmpty) return []; - - // Filter dept room list by selected location description - List filteredList = _allDeptRoomList.where((item) => item.locationDescriptionName == selectedLocationDescription!.name).toList(); - - // Convert to DepartmentRoomCombined - return filteredList - .map((item) => DepartmentRoomCombined( - deptId: item.deptId, - roomId: item.roomId, - deptName: item.deptName, - roomName: item.roomName, - locationDescriptionName: item.locationDescriptionName, - deptAndRoomName: item.deptAndRoomName, - )) - .toList(); - } + final result = await CleanerProvider().proceedOneTimeTask( + oneTimeSettingId: selectedTaskType!.id ?? 0, + siteId: _scannedLocationData!.siteId ?? 0, + buildingId: _scannedLocationData!.buildingId ?? 0, + floorId: _scannedLocationData!.floorId ?? 0, + departmentId: _scannedLocationData!.departmentId ?? 0, + roomId: _scannedLocationData!.roomId ?? 0, + ); - void _autoSelectDepartmentRoom() { - final combinations = _getDepartmentRoomCombinations(); + final isSuccess = result["success"] ?? false; + final data = result["data"]; + final message = result["message"] ?? "Unknown error occurred"; - if (combinations.isEmpty) { - setState(() { - selectedDepartmentRoom = null; - }); - return; - } + if (isSuccess && data != null) { + // Get the task ID from the response + final taskId = data['id']; - // If only one item, auto-fill - if (combinations.length == 1) { - setState(() { - selectedDepartmentRoom = combinations.first; - }); - } else { - // If more than one, auto-select first item + if (mounted && taskId != null) { + // Navigate to OneTimeTaskView + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => OneTimeTaskView(taskId: taskId), + ), + ); + } + } else { + if (mounted) { + _showErrorDialog(context, message); + } + } + } catch (e) { + if (mounted) { + _showErrorDialog(context, 'Failed to proceed. Please try again.'); + } + } finally { setState(() { - selectedDepartmentRoom = combinations.first; + _isProceeding = false; }); } } @@ -213,253 +182,92 @@ class _CleanerSearchFormViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Site Autocomplete Field - Container( - padding: EdgeInsets.symmetric( - horizontal: 16.toScreenWidth, - vertical: 8.toScreenHeight, - ), - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Site', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral20, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 4.toScreenHeight), - SizedBox( - height: 24.toScreenHeight, // Fixed height for consistency - child: Autocomplete( - optionsBuilder: (TextEditingValue textEditingValue) async { - if (textEditingValue.text.isEmpty) { - return const Iterable.empty(); - } - await _searchSites(textEditingValue.text); - return _siteOptions; - }, - displayStringForOption: (Hospital option) => option.name ?? "", - onSelected: (Hospital selected) async { - // _siteFocusNode.unfocus(); - FocusManager.instance.primaryFocus?.unfocus(); - setState(() { - selectedSite = selected; - _siteController.text = selected.name ?? ''; - // Reset dependent dropdowns - selectedBuilding = null; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _buildingOptions = []; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - - // Load buildings for selected site - if (selected.id != null) { - await _loadBuildings(selected.id!); - } - }, - fieldViewBuilder: (context, controller, focusNode, onFieldSubmitted) { - if (_siteController.text.isNotEmpty && controller.text.isEmpty) { - controller.text = _siteController.text; - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextField( - controller: controller, - // focusNode: _siteFocusNode, - focusNode: focusNode, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.black10, - ), - decoration: InputDecoration( - hintText: 'Search site...', - hintStyle: AppTextStyles.bodyText.copyWith( - color: AppColor.neutral20, - ), - filled: false, - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - contentPadding: EdgeInsets.zero, - isDense: true, - ), - onChanged: (value) { - _siteController.text = value; - }, - ), - ), - if (_isLoadingSites) - const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: AppColor.primary10, - ), - ) - else if (controller.text.isNotEmpty) - GestureDetector( - onTap: () { - controller.clear(); - _siteController.clear(); - setState(() { - selectedSite = null; - selectedBuilding = null; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _siteOptions = []; - _buildingOptions = []; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - }, - child: const Icon( - Icons.clear, - color: AppColor.neutral20, - size: 20, - ), - ), - ], - ); - }, - ), - ), - ], - ), - ), - const SizedBox(height: 18), - - // Building Dropdown - SingleItemDropDownMenu( + // Task Type Dropdown + SingleItemDropDownMenu( context: context, - title: 'Building', - initialValue: selectedBuilding, + title: 'Task Type', + initialValue: selectedTaskType, showShadow: false, showAsBottomSheet: true, backgroundColor: AppColor.fieldBgColor(context), - enabled: _buildingOptions.isNotEmpty, - loading: _isLoadingBuildings, - staticData: _buildingOptions, - onSelect: (value) async { - _siteFocusNode.unfocus(); - + enabled: _taskTypeOptions.isNotEmpty, + loading: _isLoadingTaskTypes, + staticData: _taskTypeOptions, + onSelect: (value) { setState(() { - _siteOptions = []; - print('site options length ${_siteOptions.length}'); - selectedBuilding = value; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; + selectedTaskType = value; }); - - // Load floors for selected building - if (value != null && selectedSite?.id != null && value.id != null) { - await _loadFloors(selectedSite!.id!, value.id!.toInt()); - } }, ), const SizedBox(height: 18), - // Floor Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Floor', - initialValue: selectedFloor, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _floorOptions.isNotEmpty, - loading: _isLoadingFloors, - staticData: _floorOptions, - onSelect: (value) async { - setState(() { - selectedFloor = value; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - - // Load location descriptions for selected floor - if (value != null && selectedSite?.id != null && selectedBuilding?.id != null && value.id != null) { - await _loadLocationDescriptions( - selectedSite!.id!, - selectedBuilding!.id!.toInt(), - value.id!.toInt(), - ); - } - }, + // Scan QR Code Button + GestureDetector( + onTap: selectedTaskType != null ? _onScanQRCode : null, + child: Container( + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + color: selectedTaskType != null ? Colors.white : Colors.grey[300], + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: selectedTaskType != null ? AppColor.blueStatus(context) : Colors.grey, + width: 2, + ), + ), + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth, vertical: 8.toScreenHeight), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + "scan_asset".toSvgAsset( + height: 22, + fit: BoxFit.fitHeight, + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.neutral120) + : Colors.grey, + ), + 8.width, + //we can change the implementation when merge with uiux branch + _scannedLocationData==null? "Scan QR Code".bodyText(context).custom( + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.black20) + : Colors.grey, + ):'Re Scan QR Code'.bodyText(context).custom( + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.black20) + : Colors.grey, + ), + ], + ), + ), ), const SizedBox(height: 18), - // Location Description Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Location Description', - initialValue: selectedLocationDescription, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _locationDescriptionOptions.isNotEmpty, - loading: _isLoadingLocationDescriptions, - staticData: _locationDescriptionOptions, - onSelect: (value) { - setState(() { - selectedLocationDescription = value; - selectedDepartmentRoom = null; - }); - - // Auto-select department-room based on location description - _autoSelectDepartmentRoom(); - }, - ), - const SizedBox(height: 18), + // Loading Indicator + if (_isScanning) + const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ), - // Department & Room Combined Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Department - Room', - initialValue: selectedDepartmentRoom, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _getDepartmentRoomCombinations().isNotEmpty, - staticData: _getDepartmentRoomCombinations(), - onSelect: (value) { - setState(() { - selectedDepartmentRoom = value; - }); - }, - ), + // Scanned Location Data Card + if (_scannedLocationData != null && !_isScanning) _buildLocationDataCard(context), ], ).toShadowContainer(context).paddingOnly(top: 20, start: 16, end: 16), ), ), + + // Footer Action Button FooterActionButton.footerContainer( context: context, child: AppFilledButton( - buttonColor: AppColor.primary10, - label: context.translation.search, + buttonColor:_scannedLocationData != null && !_isProceeding? AppColor.primary10: Colors.grey, + label: 'Proceed', maxWidth: true, - onPressed: _isSearching ? null : _onSubmit, - loading: _isSearching, + onPressed: (_scannedLocationData != null && !_isProceeding) ? _onProceed : null, + loading: _isProceeding, ), ), ], @@ -467,52 +275,69 @@ class _CleanerSearchFormViewState extends State { ); } - Future _onSubmit() async { - // Validate that at least site is selected - if (selectedSite == null) { - _showErrorDialog(context, 'Please select a site'); - return; - } + Widget _buildLocationDataCard(BuildContext context) { + final locationData = _scannedLocationData; - try { - setState(() { - _isSearching = true; - }); - - // Call the search API - final response = await CleanerProvider().searchPlanRecurrentTasksList( - pageSize: 10, - pageNumber: 1, - siteId: selectedSite?.id, - buildingId: selectedBuilding?.id?.toInt(), - floorId: selectedFloor?.id?.toInt(), - departmentId: selectedDepartmentRoom?.deptId, - roomId: selectedDepartmentRoom?.roomId, - ); - - final results = response['data'] as List; - final totalRows = response['totalRows'] as int; - - // Navigate to search results page with the results - Navigator.of(context).pushNamed( - CleanerSearchListView.routeName, - arguments: { - 'searchResults': results, - 'totalRows': totalRows, - 'selectedSite': selectedSite, - 'selectedBuilding': selectedBuilding, - 'selectedFloor': selectedFloor, - 'selectedLocationDescription': selectedLocationDescription, - 'selectedDepartmentRoom': selectedDepartmentRoom, - }, - ); - } catch (e) { - _showErrorDialog(context, 'Error searching. Please try again.'); - } finally { - setState(() { - _isSearching = false; - }); + if (locationData == null) { + return const SizedBox(); } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (locationData.siteName != null && locationData.siteName!.isNotEmpty) + Text( + 'Site: ${locationData.siteName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.buildingName != null && locationData.buildingName!.isNotEmpty) + Text( + 'Building: ${locationData.buildingName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.floorName != null && locationData.floorName!.isNotEmpty) + Text( + 'Floor: ${locationData.floorName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.departmentName != null && locationData.departmentName!.isNotEmpty) + Text( + 'Department: ${locationData.departmentName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.roomName != null && locationData.roomName!.isNotEmpty) + Text( + 'Room: ${locationData.roomName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ], + ), + ], + ), + ], + ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), + ], + ).toShadowContainer(context, padding: 0); } void _showErrorDialog(BuildContext context, String message) { diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart new file mode 100644 index 00000000..d1037aa2 --- /dev/null +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -0,0 +1,437 @@ +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/cleaner_module/models/one_time_task_data.dart'; +import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/cleaner_checklist_widget.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/cleaner_preview_dialog.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.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/default_app_bar.dart'; +import 'package:test_sa/new_views/pages/land_page/land_page.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; + +class OneTimeTaskView extends StatefulWidget { + static const String routeName = "/one_time_task"; + final int taskId; + + const OneTimeTaskView({Key? key, required this.taskId}) : super(key: key); + + @override + State createState() => _OneTimeTaskViewState(); +} + +class _OneTimeTaskViewState extends State { + OneTimeTaskData? _taskData; + bool _isLoading = false; + DateTime? _timerStart; + + @override + void initState() { + super.initState(); + _loadTaskData(); + } + + Future _loadTaskData() async { + setState(() { + _isLoading = true; + }); + + try { + final result = await CleanerProvider().getOneTimeTaskById(id: widget.taskId); + + if (result['success'] && result['data'] != null) { + setState(() { + _taskData = OneTimeTaskData.fromJson(result['data']); + _isLoading = false; + + // Start timer automatically if task is in progress + if (_taskData?.statusValue == 1 && _taskData?.startTime != null) { + _timerStart = DateTime.parse(_taskData!.startTime!); + } + }); + } else { + setState(() { + _isLoading = false; + }); + if (mounted) { + _showErrorDialog(context, result['message'] ?? 'Failed to load task'); + } + } + } catch (e) { + setState(() { + _isLoading = false; + }); + if (mounted) { + _showErrorDialog(context, 'Failed to load task data'); + } + } + } + + bool get isClosed => _taskData?.statusValue == 2; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'One Time Task', + onWillPopScope: isClosed + ? null + : () { + _showExitDialog(); + }, + ), + body: _isLoading + ? const ALoading() + : _taskData != null + ? SafeArea( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTaskInfoCard(context), + 16.height, + CleanerChecklistWidget( + isClosed: isClosed, + checklistItems: _convertInstructionsToChecklist(), + onChecklistChanged: (value) { + setState(() { + _updateInstructions(value); + }); + }, + capturedImageBase64: _taskData!.imageRoom, + onImageCaptured: (imageBase64) { + setState(() { + _taskData!.imageRoom = imageBase64; + }); + }, + ), + ], + ), + ), + ), + if (!isClosed) ...[ + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral20 : AppColor.white60, + textColor: context.isDark ? Colors.white : AppColor.black10, + onPressed: () => Navigator.pop(context), + ).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: _onComplete, + ).expanded, + ], + ), + ), + ] + ], + ), + ) + : NoDataFound(message: context.translation.noDataFound).center, + ).handlePopScope( + cxt: context, + showPopUp: !isClosed, + onSave: () { + Navigator.pop(context); + }, + ); + } + + Widget _buildTaskInfoCard(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (_taskData?.statusName != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor().withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + _taskData!.statusName!, + style: AppTextStyles.bodyText.copyWith( + color: _getStatusColor(), + fontWeight: FontWeight.w600, + ), + ), + ), + if (_taskData?.createdDate != null) + Text( + _formatDate(_taskData!.createdDate!), + style: AppTextStyles.tinyFont.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + ], + ), + 12.height, + if (_taskData?.oneTimeSetting?.taskTypeName != null) + Text( + _taskData!.oneTimeSetting!.taskTypeName!, + style: AppTextStyles.heading4.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + 8.height, + if (_taskData?.oneTimeSetting?.instructionDescriptionName != null) + Text( + _taskData!.oneTimeSetting!.instructionDescriptionName!, + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + 8.height, + if (_taskData?.siteName != null) + _buildInfoRow('Site', _taskData!.siteName!), + if (_taskData?.buildingName != null) + _buildInfoRow('Building', _taskData!.buildingName!), + if (_taskData?.floorName != null) + _buildInfoRow('Floor', _taskData!.floorName!), + if (_taskData?.departmentName != null) + _buildInfoRow('Department', _taskData!.departmentName!), + if (_taskData?.roomName != null) + _buildInfoRow('Room', _taskData!.roomName!), + if (_taskData?.locationDescription != null) + _buildInfoRow('Location', _taskData!.locationDescription!), + // Timer display + if (_timerStart != null && !isClosed) ...[ + 12.height, + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.primary10.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColor.primary10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Working Time:', + style: AppTextStyles.bodyText.copyWith( + fontWeight: FontWeight.w600, + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + Text( + _formatDuration(DateTime.now().difference(_timerStart!)), + style: AppTextStyles.heading4.copyWith( + color: AppColor.primary10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ], + ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), + ], + ).toShadowContainer(context, padding: 0); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text( + '$label: $value', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ); + } + + List _convertInstructionsToChecklist() { + return _taskData?.instructions?.map((instruction) { + return ChecklistItem( + id: instruction.id ?? 0, + title: instruction.instructionText ?? '', + isChecked: instruction.isDone ?? false, + isMandatory: instruction.isRequired ?? false, + comment: instruction.comment ?? '', + ); + }).toList() ?? []; + } + + void _updateInstructions(List checklist) { + _taskData?.instructions = checklist.map((item) { + return Instruction( + id: item.id, + instructionTextId: _taskData?.instructions?.firstWhere((i) => i.id == item.id).instructionTextId, + instructionText: item.title, + isDone: item.isChecked, + isRequired: item.isMandatory, + comment: item.comment, + ); + }).toList(); + } + + void _onComplete() { + // Validate mandatory items + final uncheckedMandatory = _taskData?.instructions?.where((item) => + (item.isRequired ?? false) && !(item.isDone ?? false) + ).toList() ?? []; + + if (uncheckedMandatory.isNotEmpty) { + _showErrorDialog(context, "Please complete all mandatory tasks (marked with *)"); + return; + } + + // Show preview dialog + _showPreviewDialog(); + } + + void _showPreviewDialog() { + // Calculate working hours + double totalWorkingHours = 0; + if (_timerStart != null) { + final duration = DateTime.now().difference(_timerStart!); + totalWorkingHours = duration.inSeconds / 3600.0; + } + + CleanerPreviewDialog.show( + context, + model: _convertToRecurrentWoData(), + totalWorkingHours: totalWorkingHours, + onConfirm: _performComplete, + ); + } + + // Convert OneTimeTaskData to RecurrentWoData for preview dialog compatibility + dynamic _convertToRecurrentWoData() { + return { + 'siteName': _taskData?.siteName, + 'buildingName': _taskData?.buildingName, + 'floorName': _taskData?.floorName, + 'departmentName': _taskData?.departmentName, + 'roomName': _taskData?.roomName, + 'locationDescription': _taskData?.locationDescription, + 'taskNo': 'OT-${_taskData?.id}', + 'title': _taskData?.oneTimeSetting?.taskTypeName, + }; + } + + Future _performComplete() async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AppLazyLoading(), + ); + + try { + final instructionsPayload = _taskData?.instructions?.map((instruction) { + return { + 'id': instruction.id, + 'instructionTextId': instruction.instructionTextId, + 'instructionText': instruction.instructionText, + 'isRequired': instruction.isRequired, + 'isDone': instruction.isDone, + 'comment': instruction.comment, + }; + }).toList() ?? []; + + final result = await CleanerProvider().completeOneTimeTask( + id: _taskData!.id!, + instructions: instructionsPayload, + imageRoom: _taskData?.imageRoom, + ); + + if (mounted) Navigator.pop(context); // Close loading dialog + + if (result['success']) { + if (mounted) { + // Navigate to dashboard + Navigator.of(context).pushNamedAndRemoveUntil( + LandPage.routeName, + (routes) => false, + ); + } + } else { + if (mounted) { + _showErrorDialog(context, result['message'] ?? 'Failed to complete task'); + } + } + } catch (e) { + if (mounted) Navigator.pop(context); + if (mounted) { + _showErrorDialog(context, 'Failed to complete task. Please try again.'); + } + } + } + + void _showExitDialog() { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: 'Do you want to exit without completing?', + title: 'Confirm', + onTap: () { + Navigator.pop(context); // Close dialog + Navigator.pop(context); // Exit screen + }, + ), + ); + } + + void _showErrorDialog(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: message, + title: 'Error', + onTap: () => Navigator.pop(context), + ), + ); + } + + Color _getStatusColor() { + if (_taskData?.statusValue == 2) { + return Colors.green; + } + return AppColor.primary10; + } + + String _formatDate(String dateStr) { + try { + final date = DateTime.parse(dateStr); + return '${date.day}/${date.month}/${date.year}'; + } catch (e) { + return dateStr; + } + } + + String _formatDuration(Duration duration) { + final hours = duration.inHours; + final minutes = duration.inMinutes.remainder(60); + final seconds = duration.inSeconds.remainder(60); + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/modules/cleaner_module/provider/cleaner_provider.dart b/lib/modules/cleaner_module/provider/cleaner_provider.dart index ec6fff3f..18a442b3 100644 --- a/lib/modules/cleaner_module/provider/cleaner_provider.dart +++ b/lib/modules/cleaner_module/provider/cleaner_provider.dart @@ -102,6 +102,7 @@ class CleanerProvider extends ChangeNotifier { int? floorId, int? departmentId, int? roomId, + int? taskTypeId, }) async { Response response; try { @@ -115,6 +116,7 @@ class CleanerProvider extends ChangeNotifier { "floorId": floorId, "departmentId": departmentId, "roomId": roomId, + "taskTypeId": taskTypeId, }, ); @@ -203,6 +205,183 @@ class CleanerProvider extends ChangeNotifier { } } + /// Scan QR Code and get task details + /// Returns a map with success status, message, and data from backend + Future> scanQRCode({ + required String qrCode, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.scanQRCode, + body: { + "qrCode": qrCode, + }, + showToast: false, // Disable automatic toast message + ); + + // Parse the response body to extract the message + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "QR code scanned successfully", + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "QR code scan failed", + }; + } + } catch (error) { + debugPrint('Error scanning QR code: $error'); + return { + "success": false, + "data": null, + "message": "Failed to scan QR code. Please check your connection and try again.", + }; + } + } + + /// Proceed with OneTime Task request + /// Returns a map with success status, message, and data from backend + Future> proceedOneTimeTask({ + required int oneTimeSettingId, + required int siteId, + required int buildingId, + required int floorId, + required int departmentId, + required int roomId, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.proceedRequest, + body: { + "oneTimeSettingId": oneTimeSettingId, + "siteId": siteId, + "buildingId": buildingId, + "floorId": floorId, + "departmentId": departmentId, + "roomId": roomId, + }, + showToast: false, // Disable automatic toast message + ); + + // Parse the response body to extract the message + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Request proceeded successfully", + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to proceed with request", + }; + } + } catch (error) { + debugPrint('Error proceeding with request: $error'); + return { + "success": false, + "data": null, + "message": "Failed to proceed with request. Please check your connection and try again.", + }; + } + } + + /// Get OneTime Task by ID using getRequestById endpoint + Future> getOneTimeTaskById({ + required int id, + }) async { + Response response; + try { + response = await ApiManager.instance.get( + '${URLs.getRequestById}/$id', + ); + + // Parse the response body + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Task retrieved successfully", + }; + } else { + // Error response + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to retrieve task", + }; + } + } catch (error) { + debugPrint('Error getting one time task: $error'); + return { + "success": false, + "data": null, + "message": "Failed to retrieve task. Please check your connection and try again.", + }; + } + } + + /// Complete OneTime Task + Future> completeOneTimeTask({ + required int id, + required List> instructions, + String? imageRoom, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.completeRequest, + body: { + "id": id, + "instructions": instructions, + "imageRoom": imageRoom, + }, + showToast: false, + ); + + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Task completed successfully", + }; + } else { + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to complete task", + }; + } + } catch (error) { + debugPrint('Error completing one time task: $error'); + return { + "success": false, + "data": null, + "message": "Failed to complete task. Please check your connection and try again.", + }; + } + } + void reset() { _isLoading = false; _searchResults = null; diff --git a/lib/modules/cleaner_module/provider/task_type_provider.dart b/lib/modules/cleaner_module/provider/task_type_provider.dart new file mode 100644 index 00000000..624e5c36 --- /dev/null +++ b/lib/modules/cleaner_module/provider/task_type_provider.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.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/modules/cleaner_module/models/task_type.dart'; + +class TaskTypeProvider extends ChangeNotifier { + //reset provider data + void reset() { + _taskTypes = null; + _stateCode = null; + } + + // state code of current request to define error message + int? _stateCode; + int? get stateCode => _stateCode; + + // contain task types data + List? _taskTypes; + List? get taskTypes => _taskTypes; + + // loading state + bool _loading = false; + bool get isLoading => _loading; + + set isLoading(bool isLoading) { + _loading = isLoading; + notifyListeners(); + } + + /// Get task types + Future getTaskTypes() async { + if (_loading == true) return -2; + _loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getTaskType); + _stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List taskTypesListJson = json.decode(response.body)["data"]; + _taskTypes = taskTypesListJson.map((taskType) => TaskType.fromJson(taskType)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + } + + /// Get task types list directly without provider state + Future> getTaskTypesList() async { + Response response; + try { + response = await ApiManager.instance.get(URLs.getTaskType); + + if (response.statusCode >= 200 && response.statusCode < 300) { + List taskTypesListJson = json.decode(response.body)["data"]; + return taskTypesListJson.map((taskType) => TaskType.fromJson(taskType)).toList(); + } + return []; + } catch (error) { + return []; + } + } +} + From c35162fd88cfec22faea14e5c9e41771b1f2fe8b Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:49:14 +0300 Subject: [PATCH 08/73] cleaner module implementation new flow complete --- lib/dashboard_latest/dashboard_provider.dart | 4 +- .../widgets/progress_fragment.dart | 2 +- .../widgets/request_category_fragment.dart | 5 +- .../widgets/request_category_list.dart | 3 + lib/main.dart | 2 - .../models/cleaner_data_model.dart | 0 .../models/cleaner_form_model.dart | 24 - .../models/one_time_task_data.dart | 19 +- .../pages/cleaner_request_item_view.dart | 69 --- .../pages/cleaner_search_form_view.dart | 98 ++-- .../pages/cleaner_search_list_view.dart | 183 ------- .../pages/one_time_task_preview_screen.dart | 373 ++++++++++++++ .../pages/one_time_task_view.dart | 326 +++++++------ .../provider/cleaner_provider.dart | 7 +- .../provider/fms_building_provider.dart | 83 ---- .../provider/fms_floor_provider.dart | 85 ---- .../fms_location_description_provider.dart | 140 ------ .../widgets/cleaner_checklist_widget.dart | 377 ++++++--------- .../widgets/cleaner_preview_dialog.dart | 457 ------------------ .../widgets/multi_image_picker.dart | 218 +++++++++ .../widgets/one_time_task_item_view.dart | 137 ++++++ .../recurrent_work_order_view.dart | 179 ++----- .../common_widgets/app_bottom_nav_bar.dart | 4 +- .../requests/recurrent_wo_item_view.dart | 42 +- .../requests/request_paginated_listview.dart | 3 + .../widgets/request_item_view_list.dart | 3 + 26 files changed, 1174 insertions(+), 1669 deletions(-) delete mode 100644 lib/modules/cleaner_module/models/cleaner_data_model.dart delete mode 100644 lib/modules/cleaner_module/models/cleaner_form_model.dart delete mode 100644 lib/modules/cleaner_module/pages/cleaner_request_item_view.dart delete mode 100644 lib/modules/cleaner_module/pages/cleaner_search_list_view.dart create mode 100644 lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart delete mode 100644 lib/modules/cleaner_module/provider/fms_building_provider.dart delete mode 100644 lib/modules/cleaner_module/provider/fms_floor_provider.dart delete mode 100644 lib/modules/cleaner_module/provider/fms_location_description_provider.dart delete mode 100644 lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart create mode 100644 lib/modules/cleaner_module/widgets/multi_image_picker.dart create mode 100644 lib/modules/cleaner_module/widgets/one_time_task_item_view.dart diff --git a/lib/dashboard_latest/dashboard_provider.dart b/lib/dashboard_latest/dashboard_provider.dart index 92ecd60e..c104de4f 100644 --- a/lib/dashboard_latest/dashboard_provider.dart +++ b/lib/dashboard_latest/dashboard_provider.dart @@ -418,9 +418,9 @@ class CategoryTabs { if (userType == UsersTypes.houseKeeper) { // tabs.add(CategoryTabs(label: 'Upcoming', tag: 0, id: 0)); // tabs.add(CategoryTabs('Open', 1)); - tabs.add(CategoryTabs(label: 'UnAssigned', tag: 1, id: 1)); + // tabs.add(CategoryTabs(label: 'UnAssigned', tag: 1, id: 1)); tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 2)); - tabs.add(CategoryTabs(label: 'Closed', tag: 4, id: 3)); + tabs.add(CategoryTabs(label: 'Completed', tag: 4, id: 3)); return tabs; } diff --git a/lib/dashboard_latest/widgets/progress_fragment.dart b/lib/dashboard_latest/widgets/progress_fragment.dart index ed7443c2..3316617a 100644 --- a/lib/dashboard_latest/widgets/progress_fragment.dart +++ b/lib/dashboard_latest/widgets/progress_fragment.dart @@ -193,7 +193,7 @@ class ProgressFragment extends StatelessWidget { total = (snapshot.dashboardCount?.data?.countClose ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); chartData = [ ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.orange70), - ChartData('Closed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.green70), + ChartData('Completed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.green70), // ChartData('Open', snapshot.dashboardCount?.data?.countOpen?.toDouble() ?? 0.0, AppColor.blueStatus(context)), ]; } else { diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index 54c7f1a8..2eeb897b 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -5,6 +5,7 @@ import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/dashboard_latest/widgets/request_category_list.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/enums/user_types.dart'; @@ -57,8 +58,8 @@ class RequestCategoryFragment extends StatelessWidget { ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - context.translation.allWorkOrder.heading4(context), - 'search'.toSvgAsset(width: 20, height: 20).onPress(() { + 'All Tasks'.addTranslation.heading4(context), + 'add_icon'.toSvgAsset(width: 20, height: 20).onPress(() { Navigator.of(context).pushNamed('/cleaner_search'); }), ], diff --git a/lib/dashboard_latest/widgets/request_category_list.dart b/lib/dashboard_latest/widgets/request_category_list.dart index e48f9f7f..3f7808d6 100644 --- a/lib/dashboard_latest/widgets/request_category_list.dart +++ b/lib/dashboard_latest/widgets/request_category_list.dart @@ -6,6 +6,7 @@ import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; import 'package:test_sa/modules/asset_delivery_module/pages/asset_delivery_item_view.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/one_time_task_item_view.dart'; import 'package:test_sa/modules/demo_module/demo_request_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/loan_module/pages/loan_equipment_item_view.dart'; @@ -76,6 +77,8 @@ class RequestCategoryList extends StatelessWidget { return MedicalGasInspectionItemView(requestData: request); case 15: return DemoRequestItemView(requestData: request); + case 17: + return OneTimeTaskItemView(requestData: request); default: return Container( height: 100, diff --git a/lib/main.dart b/lib/main.dart index 40ba2eda..b6d04cdb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -140,7 +140,6 @@ import 'providers/lookups/yes_no_lookup_provider.dart'; import 'providers/service_request_providers/loan_availability_provider.dart'; import 'providers/service_request_providers/reject_reason_provider.dart'; import 'modules/cleaner_module/pages/cleaner_search_form_view.dart'; -import 'modules/cleaner_module/pages/cleaner_search_list_view.dart'; import 'modules/cleaner_module/provider/cleaner_provider.dart'; // class MyHttpOverrides extends HttpOverrides { @@ -408,7 +407,6 @@ class MyApp extends StatelessWidget { CreateMedicalGasRequestPage.id: (_) => CreateMedicalGasRequestPage(), CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(), CleanerSearchFormView.routeName: (_) => const CleanerSearchFormView(), - CleanerSearchListView.routeName: (_) => const CleanerSearchListView(), }, ), ), diff --git a/lib/modules/cleaner_module/models/cleaner_data_model.dart b/lib/modules/cleaner_module/models/cleaner_data_model.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/modules/cleaner_module/models/cleaner_form_model.dart b/lib/modules/cleaner_module/models/cleaner_form_model.dart deleted file mode 100644 index f8f7c98d..00000000 --- a/lib/modules/cleaner_module/models/cleaner_form_model.dart +++ /dev/null @@ -1,24 +0,0 @@ -// Cleaner request search model for mock/static data -class CleanerRequest { - final String priority; - final String date; - final String time; - final String roomNo; - final String site; - final String building; - final String floor; - final String clinic; - final String? taskNo; - - CleanerRequest({ - required this.priority, - required this.date, - required this.time, - required this.roomNo, - required this.site, - required this.building, - required this.floor, - required this.clinic, - this.taskNo, - }); -} diff --git a/lib/modules/cleaner_module/models/one_time_task_data.dart b/lib/modules/cleaner_module/models/one_time_task_data.dart index 817ae685..f348300a 100644 --- a/lib/modules/cleaner_module/models/one_time_task_data.dart +++ b/lib/modules/cleaner_module/models/one_time_task_data.dart @@ -24,7 +24,8 @@ class OneTimeTaskData { List? attachments; String? createdDate; String? createdBy; - String? imageRoom; // For captured image + String? imageRoom; // For backward compatibility - deprecated + List? imageRooms; // For multiple captured images (max 2) OneTimeTaskData({ this.id, @@ -53,6 +54,7 @@ class OneTimeTaskData { this.createdDate, this.createdBy, this.imageRoom, + this.imageRooms, }); factory OneTimeTaskData.fromJson(Map json) { @@ -87,6 +89,7 @@ class OneTimeTaskData { createdDate: json['createdDate'], createdBy: json['createdBy'], imageRoom: json['imageRoom'], + imageRooms: json['imageRooms'] != null ? List.from(json['imageRooms']) : null, ); } @@ -118,6 +121,7 @@ class OneTimeTaskData { 'createdDate': createdDate, 'createdBy': createdBy, 'imageRoom': imageRoom, + 'imageRooms': imageRooms, }; } } @@ -204,12 +208,16 @@ class Instruction { class Attachment { int? id; - String? fileName; - String? fileUrl; + String? name; + String? originalName; + String? fileName; // For backward compatibility + String? fileUrl; // For backward compatibility String? fileType; Attachment({ this.id, + this.name, + this.originalName, this.fileName, this.fileUrl, this.fileType, @@ -218,6 +226,8 @@ class Attachment { factory Attachment.fromJson(Map json) { return Attachment( id: json['id'], + name: json['name'], + originalName: json['originalName'], fileName: json['fileName'], fileUrl: json['fileUrl'], fileType: json['fileType'], @@ -227,10 +237,11 @@ class Attachment { Map toJson() { return { 'id': id, + 'name': name, + 'originalName': originalName, 'fileName': fileName, 'fileUrl': fileUrl, 'fileType': fileType, }; } } - diff --git a/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart b/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart deleted file mode 100644 index 0ffc8e96..00000000 --- a/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart +++ /dev/null @@ -1,69 +0,0 @@ -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/new_views/app_style/app_color.dart'; -import '../models/cleaner_task_search_result.dart'; - -class CleanerRequestItemView extends StatelessWidget { - final CleanerTaskSearchResult result; - final VoidCallback? onTap; - final bool showShadow; - - const CleanerRequestItemView({Key? key, required this.result, this.onTap, this.showShadow = true}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Task Number and Date Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (result.taskNo != null && result.taskNo!.isNotEmpty) result.taskNo!.heading5(context), - if (result.createdDate != null) - Text( - result.createdDate!.toServiceRequestCardFormat, - textAlign: TextAlign.end, - style: AppTextStyles.tinyFont.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral50, - ), - ), - ], - ), - 8.height, - - // Location Information - if (result.siteName != null && result.siteName!.isNotEmpty) 'Site: ${result.siteName}'.bodyText(context), - if (result.buildingName != null && result.buildingName!.isNotEmpty) 'Building: ${result.buildingName}'.bodyText(context), - if (result.floorName != null && result.floorName!.isNotEmpty) 'Floor: ${result.floorName}'.bodyText(context), - if (result.departmentName != null && result.departmentName!.isNotEmpty) 'Department: ${result.departmentName}'.bodyText(context), - if (result.roomName != null && result.roomName!.isNotEmpty) 'Room: ${result.roomName}'.bodyText(context), - if (result.locationDescription != null && result.locationDescription!.isNotEmpty) 'Location: ${result.locationDescription}'.bodyText(context), - - 8.height, - - // View Details Link - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - 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(onTap); - } -} diff --git a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart index 9cc1f1d8..e2761c08 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart @@ -5,6 +5,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cleaner_module/models/task_type.dart'; import 'package:test_sa/modules/cleaner_module/models/qr_scan_location_data.dart'; +import 'package:test_sa/modules/cleaner_module/models/one_time_task_data.dart'; import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; import 'package:test_sa/modules/cleaner_module/provider/task_type_provider.dart'; import 'package:test_sa/modules/cleaner_module/pages/one_time_task_view.dart'; @@ -142,10 +143,16 @@ class _CleanerSearchFormViewState extends State { final taskId = data['id']; if (mounted && taskId != null) { - // Navigate to OneTimeTaskView + // Convert the data to OneTimeTaskData and navigate with the data + final taskData = OneTimeTaskData.fromJson(data); + + // Navigate to OneTimeTaskView with the data Navigator.of(context).pushReplacement( MaterialPageRoute( - builder: (context) => OneTimeTaskView(taskId: taskId), + builder: (context) => OneTimeTaskView( + taskId: taskId, + initialData: taskData, + ), ), ); } @@ -289,51 +296,50 @@ class _CleanerSearchFormViewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (locationData.siteName != null && locationData.siteName!.isNotEmpty) - Text( - 'Site: ${locationData.siteName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - if (locationData.buildingName != null && locationData.buildingName!.isNotEmpty) - Text( - 'Building: ${locationData.buildingName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - if (locationData.floorName != null && locationData.floorName!.isNotEmpty) - Text( - 'Floor: ${locationData.floorName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - if (locationData.departmentName != null && locationData.departmentName!.isNotEmpty) - Text( - 'Department: ${locationData.departmentName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - if (locationData.roomName != null && locationData.roomName!.isNotEmpty) - Text( - 'Room: ${locationData.roomName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - ], + if (locationData.siteName != null && locationData.siteName!.isNotEmpty) + Text( + 'Site: ${locationData.siteName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), - ], - ), + ), + if (locationData.buildingName != null && locationData.buildingName!.isNotEmpty) + Text( + 'Building: ${locationData.buildingName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.floorName != null && locationData.floorName!.isNotEmpty) + Text( + 'Floor: ${locationData.floorName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.departmentName != null && locationData.departmentName!.isNotEmpty) + Text( + 'Department: ${locationData.departmentName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.roomName != null && locationData.roomName!.isNotEmpty) + Text( + 'Room: ${locationData.roomName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.locationDescription != null && locationData.locationDescription!.isNotEmpty) + Text( + 'Location Description: ${locationData.locationDescription}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + softWrap: true, + maxLines: null, + ), ], ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), ], diff --git a/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart deleted file mode 100644 index 87e25e22..00000000 --- a/lib/modules/cleaner_module/pages/cleaner_search_list_view.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:test_sa/extensions/context_extension.dart'; -import 'package:test_sa/extensions/string_extensions.dart'; -import 'package:test_sa/extensions/widget_extensions.dart'; -import 'package:test_sa/models/hospital.dart'; -import 'package:test_sa/models/lookup.dart'; -import 'package:test_sa/models/new_models/building.dart'; -import 'package:test_sa/models/new_models/department_room_combined.dart'; -import 'package:test_sa/models/new_models/floor.dart'; -import 'package:test_sa/modules/cleaner_module/models/cleaner_task_search_result.dart'; -import 'package:test_sa/modules/cleaner_module/pages/cleaner_search_details_view.dart'; -import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; -import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; -import 'package:test_sa/views/widgets/loaders/app_loading.dart'; -import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; - -import 'cleaner_request_item_view.dart'; - -class CleanerSearchListView extends StatefulWidget { - static const String routeName = '/cleaner_search_list'; - - const CleanerSearchListView({Key? key}) : super(key: key); - - @override - State createState() => _CleanerSearchListViewState(); -} - -class _CleanerSearchListViewState extends State { - List _searchResults = []; - bool _isLoading = false; - bool _isLoadingMore = false; - bool _hasMoreData = true; - int _totalRows = 0; // Add this to track total rows from API - - // Pagination - int _currentPage = 1; - final int _pageSize = 10; - - // Search filters (stored for pagination) - Hospital? _selectedSite; - Building? _selectedBuilding; - Floor? _selectedFloor; - Lookup? _selectedLocationDescription; - DepartmentRoomCombined? _selectedDepartmentRoom; - - final ScrollController _scrollController = ScrollController(); - final CleanerProvider _cleanerProvider = CleanerProvider(); - - @override - void initState() { - super.initState(); - _scrollController.addListener(_onScroll); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - // Get the search results and filters from navigation arguments - final args = ModalRoute.of(context)?.settings.arguments as Map?; - if (args != null && args['searchResults'] != null) { - final results = args['searchResults'] as List; - _searchResults = results.map((json) => CleanerTaskSearchResult.fromJson(json)).toList(); - - // Get totalRows from API response - _totalRows = args['totalRows'] as int? ?? 0; - - // Store search filters for pagination - _selectedSite = args['selectedSite'] as Hospital?; - _selectedBuilding = args['selectedBuilding'] as Building?; - _selectedFloor = args['selectedFloor'] as Floor?; - _selectedLocationDescription = args['selectedLocationDescription'] as Lookup?; - _selectedDepartmentRoom = args['selectedDepartmentRoom'] as DepartmentRoomCombined?; - - // Check if there's more data based on totalRows - _hasMoreData = _searchResults.length < _totalRows; - } - } - - void _onScroll() { - if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { - // User is near the bottom, load more data - if (!_isLoadingMore && _hasMoreData && !_isLoading) { - _loadMoreData(); - } - } - } - - Future _loadMoreData() async { - if (_isLoadingMore) return; - - setState(() { - _isLoadingMore = true; - }); - - try { - _currentPage++; - - final response = await _cleanerProvider.searchPlanRecurrentTasksList( - pageSize: _pageSize, - pageNumber: _currentPage, - siteId: _selectedSite?.id, - buildingId: _selectedBuilding?.id?.toInt(), - floorId: _selectedFloor?.id?.toInt(), - departmentId: _selectedDepartmentRoom?.deptId, - roomId: _selectedDepartmentRoom?.roomId, - ); - - final results = response['data'] as List; - final totalRows = response['totalRows'] as int? ?? 0; - - final newResults = results.map((json) => CleanerTaskSearchResult.fromJson(json)).toList(); - - setState(() { - _searchResults.addAll(newResults); - _totalRows = totalRows; - // Check if we have loaded all data based on totalRows - _hasMoreData = _searchResults.length < _totalRows; - _isLoadingMore = false; - }); - } catch (e) { - setState(() { - _isLoadingMore = false; - }); - debugPrint('Error loading more data: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: DefaultAppBar( - title: 'Search Results'.addTranslation, - onBackPress: () => Navigator.pop(context), - ), - body: SafeArea( - child: Column( - children: [ - Expanded( - child: (_searchResults.isEmpty && !_isLoading) - ? NoDataFound(message: context.translation.noDataFound).center - : ListView.separated( - controller: _scrollController, - padding: const EdgeInsets.all(16), - itemCount: _searchResults.length + (_isLoadingMore ? 1 : 0), - separatorBuilder: (_, __) => const SizedBox(height: 16), - itemBuilder: (context, index) { - if (_isLoading && _searchResults.isEmpty) { - return const SizedBox().toRequestShimmer(context, _isLoading); - } - - // Show loading indicator at the bottom - if (index == _searchResults.length) { - return const ALoading().center; - } - - final result = _searchResults[index]; - - return CleanerRequestItemView( - result: result, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CleanerSearchDetailsView(requestId: result.id ?? 0, taskResult: null), - ), - ); - }, - ); - }, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart new file mode 100644 index 00000000..18e4d0ad --- /dev/null +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -0,0 +1,373 @@ +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/extensions/text_extensions.dart'; +import 'package:test_sa/modules/cleaner_module/models/one_time_task_data.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.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/controllers/api_routes/urls.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'dart:convert'; + +class OneTimeTaskPreviewScreen extends StatelessWidget { + static const String routeName = "/one_time_task_preview"; + + final OneTimeTaskData taskData; + final double totalWorkingHours; + final VoidCallback onConfirm; + + const OneTimeTaskPreviewScreen({ + Key? key, + required this.taskData, + required this.totalWorkingHours, + required this.onConfirm, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + // Debug logging + print('DEBUG Preview Screen:'); + print('imageRooms count: ${taskData.imageRooms?.length ?? 0}'); + print('attachments count: ${taskData.attachments?.length ?? 0}'); + + // Determine which images to show: captured images (imageRooms) or API attachments + final hasImageRooms = taskData.imageRooms != null && taskData.imageRooms!.isNotEmpty; + final hasAttachments = taskData.attachments != null && taskData.attachments!.isNotEmpty; + + return Scaffold( + appBar: const DefaultAppBar( + title: 'Preview', + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Task Information Card + _buildTaskInfoCard(context), + 16.height, + + // Working Hours Card + if (totalWorkingHours > 0) ...[ + _buildWorkingHoursCard(context), + 16.height, + ], + + // Checklist Card + _buildChecklistCard(context), + 16.height, + + // Captured Images Card + if (hasImageRooms) ...[ + _buildImagesCard(context), + 16.height, + ], + + // Attachments Card (from API) + if (hasAttachments) ...[ + _buildAttachmentsCard(context), + 16.height, + ], + ], + ), + ), + ), + + // Footer with Confirm Button + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: context.translation.confirm, + buttonColor: AppColor.primary10, + onPressed: onConfirm, + ), + ), + ], + ), + ), + ); + } + + Widget _buildTaskInfoCard(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Task Information', + style: AppTextStyles.heading5.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + 12.height, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (taskData.oneTimeSetting?.taskTypeName != null) _buildInfoRow(context, 'Task Type', taskData.oneTimeSetting!.taskTypeName!), + if (taskData.siteName != null) _buildInfoRow(context, 'Site', taskData.siteName!), + if (taskData.buildingName != null) _buildInfoRow(context, 'Building', taskData.buildingName!), + if (taskData.floorName != null) _buildInfoRow(context, 'Floor', taskData.floorName!), + if (taskData.departmentName != null) _buildInfoRow(context, 'Department', taskData.departmentName!), + if (taskData.roomName != null) _buildInfoRow(context, 'Room', taskData.roomName!), + if (taskData.locationDescription != null) _buildInfoRow(context, 'Location', taskData.locationDescription!), + ], + ).paddingAll(16), + ], + ).toShadowContainer(context, padding: 16); + } + + Widget _buildWorkingHoursCard(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Working Hours', + style: AppTextStyles.heading5.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Working Hours', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + Text( + '${totalWorkingHours.toStringAsFixed(2)} hrs', + style: AppTextStyles.heading6.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w600, + ), + ), + ], + ).paddingAll(12), + ], + ).toShadowContainer(context, padding: 12); + } + + Widget _buildChecklistCard(BuildContext context) { + final completedInstructions = taskData.instructions?.where((i) => i.isDone == true).toList() ?? []; + final totalInstructions = taskData.instructions?.length ?? 0; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Checklist', + style: AppTextStyles.heading5.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + Text( + '${completedInstructions.length}/$totalInstructions', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + 12.height, + ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemCount: completedInstructions.length, + separatorBuilder: (context, index) => const Divider(height: 1, color: AppColor.neutral30, thickness: 1).paddingOnly(top: 12, bottom: 12), + itemBuilder: (context, index) { + final instruction = completedInstructions[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.check_circle, + color: AppColor.primary10, + size: 20, + ), + 8.width, + Expanded( + child: Text( + instruction.instructionText ?? '', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ), + ], + ), + if (instruction.comment != null && instruction.comment!.isNotEmpty) ...[ + 8.height, + Container( + width: double.infinity, // Force full width + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Comment:', + style: AppTextStyles.tinyFont.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + 4.height, + Text( + instruction.comment!, + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ], + ), + ), + ], + ], + ); + }, + ).paddingAll(16), + ], + ).toShadowContainer(context, padding: 16); + } + + Widget _buildImagesCard(BuildContext context) { + // Show captured images (base64 from camera) + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Captured Images', + style: AppTextStyles.heading5.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + 16.height, + Wrap( + spacing: 8, + runSpacing: 8, + children: taskData.imageRooms!.map((imageString) { + return GestureDetector( + onTap: () { + // Show full screen image + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + backgroundColor: Colors.black, + iconTheme: const IconThemeData(color: Colors.white), + ), + body: Center( + child: InteractiveViewer( + child: _buildImage(imageString), + ), + ), + ), + ), + ); + }, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: context.isDark ? AppColor.neutral20 : AppColor.neutral30, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: _buildImage(imageString), + ), + ), + ); + }).toList(), + ), + ], + ).toShadowContainer(context, padding: 16); + } + + Widget _buildAttachmentsCard(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Attachments', + style: AppTextStyles.heading5.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + fontWeight: FontWeight.w600, + ), + ), + FilesList( + images: taskData.attachments!.map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '').toList(), + showAsListView: true, + ), + ], + ).toShadowContainer(context, padding: 16); + } + + Widget _buildImage(String imageString) { + try { + // imageString format: "filename|base64data" + final parts = imageString.split('|'); + final base64Data = parts.length > 1 ? parts[1] : imageString; + return Image.memory( + base64Decode(base64Data), + fit: BoxFit.cover, + ); + } catch (e) { + return const Icon(Icons.broken_image, size: 48); + } + } + + Widget _buildInfoRow(BuildContext context, String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100, + child: Text( + '$label:', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + ), + Expanded( + child: Text( + value, + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index d1037aa2..e8e1ce28 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -1,12 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/dashboard_latest/dashboard_provider.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/extensions/string_extensions.dart'; import 'package:test_sa/modules/cleaner_module/models/one_time_task_data.dart'; import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; import 'package:test_sa/modules/cleaner_module/widgets/cleaner_checklist_widget.dart'; -import 'package:test_sa/modules/cleaner_module/widgets/cleaner_preview_dialog.dart'; +import 'package:test_sa/modules/cleaner_module/pages/one_time_task_preview_screen.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -16,12 +21,16 @@ import 'package:test_sa/new_views/pages/land_page/land_page.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; class OneTimeTaskView extends StatefulWidget { static const String routeName = "/one_time_task"; final int taskId; + final OneTimeTaskData? initialData; - const OneTimeTaskView({Key? key, required this.taskId}) : super(key: key); + const OneTimeTaskView({Key? key, required this.taskId, this.initialData}) : super(key: key); @override State createState() => _OneTimeTaskViewState(); @@ -31,11 +40,25 @@ class _OneTimeTaskViewState extends State { OneTimeTaskData? _taskData; bool _isLoading = false; DateTime? _timerStart; + List? _checklistItems; // Cache checklist items @override void initState() { super.initState(); - _loadTaskData(); + // Use initial data if provided, otherwise load from API + if (widget.initialData != null) { + _taskData = widget.initialData; + + // Initialize imageRooms as empty list if null + _taskData?.imageRooms ??= []; + + // Start timer automatically if task is in progress + if (_taskData?.statusValue == 1 && _taskData?.startTime != null) { + _timerStart = DateTime.parse(_taskData!.startTime!); + } + } else { + _loadTaskData(); + } } Future _loadTaskData() async { @@ -49,6 +72,19 @@ class _OneTimeTaskViewState extends State { if (result['success'] && result['data'] != null) { setState(() { _taskData = OneTimeTaskData.fromJson(result['data']); + + // Initialize imageRooms as empty list if null + _taskData?.imageRooms ??= []; + + // Convert attachments from API to imageRooms format for display + if ((_taskData?.imageRooms?.isEmpty ?? true) && + (_taskData?.attachments != null && _taskData!.attachments!.isNotEmpty)) { + _taskData!.imageRooms = _taskData!.attachments! + .map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '') + .where((url) => url.isNotEmpty) + .toList(); + } + _isLoading = false; // Start timer automatically if task is in progress @@ -80,12 +116,18 @@ class _OneTimeTaskViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: DefaultAppBar( - title: 'One Time Task', - onWillPopScope: isClosed - ? null - : () { - _showExitDialog(); - }, + title: 'One Time Task'.addTranslation, + onWillPopScope: null, + onBackPress: (){ + WidgetsBinding.instance.addPostFrameCallback((_) { + UserProvider userProvider = Provider.of(context, listen: false); + DashBoardProvider dashBoardProvider = Provider.of(context, listen: false); + dashBoardProvider.getDashBoardCount(usersType:userProvider.user!.type!); + dashBoardProvider.resetRequestDataList(); + dashBoardProvider.getRequestDetail(usersType: userProvider.user!.type!, status: dashBoardProvider.tabs[dashBoardProvider.currentListIndex].tag); + }); + Navigator.pop(context); + }, ), body: _isLoading ? const ALoading() @@ -110,10 +152,10 @@ class _OneTimeTaskViewState extends State { _updateInstructions(value); }); }, - capturedImageBase64: _taskData!.imageRoom, - onImageCaptured: (imageBase64) { + capturedImages: _taskData!.imageRooms ?? [], + onImagesChanged: (images) { setState(() { - _taskData!.imageRoom = imageBase64; + _taskData!.imageRooms = images; }); }, ), @@ -124,22 +166,10 @@ class _OneTimeTaskViewState extends State { if (!isClosed) ...[ FooterActionButton.footerContainer( context: context, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - AppFilledButton( - label: context.translation.save, - buttonColor: context.isDark ? AppColor.neutral20 : AppColor.white60, - textColor: context.isDark ? Colors.white : AppColor.black10, - onPressed: () => Navigator.pop(context), - ).expanded, - 12.width, - AppFilledButton( - label: context.translation.complete, - buttonColor: AppColor.primary10, - onPressed: _onComplete, - ).expanded, - ], + child: AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: _onComplete, ), ), ] @@ -149,7 +179,7 @@ class _OneTimeTaskViewState extends State { : NoDataFound(message: context.translation.noDataFound).center, ).handlePopScope( cxt: context, - showPopUp: !isClosed, + showPopUp: false, onSave: () { Navigator.pop(context); }, @@ -166,25 +196,19 @@ class _OneTimeTaskViewState extends State { children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_taskData?.statusName != null) - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: _getStatusColor().withOpacity(0.1), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - _taskData!.statusName!, - style: AppTextStyles.bodyText.copyWith( - color: _getStatusColor(), - fontWeight: FontWeight.w600, - ), - ), + StatusLabel( + radius: 4, + label: _taskData!.statusName!, + textColor: AppColor.getHistoryLogStatusTextColorByName(_taskData!.statusName!), + backgroundColor: AppColor.getHistoryLogStatusColorByName(_taskData!.statusName!), ), if (_taskData?.createdDate != null) Text( - _formatDate(_taskData!.createdDate!), + _taskData!.createdDate!.toString().toServiceRequestCardFormat, + textAlign: TextAlign.end, style: AppTextStyles.tinyFont.copyWith( color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, ), @@ -208,47 +232,24 @@ class _OneTimeTaskViewState extends State { ), ), 8.height, - if (_taskData?.siteName != null) - _buildInfoRow('Site', _taskData!.siteName!), - if (_taskData?.buildingName != null) - _buildInfoRow('Building', _taskData!.buildingName!), - if (_taskData?.floorName != null) - _buildInfoRow('Floor', _taskData!.floorName!), - if (_taskData?.departmentName != null) - _buildInfoRow('Department', _taskData!.departmentName!), - if (_taskData?.roomName != null) - _buildInfoRow('Room', _taskData!.roomName!), - if (_taskData?.locationDescription != null) - _buildInfoRow('Location', _taskData!.locationDescription!), - // Timer display + if (_taskData?.siteName != null) _buildInfoRow('Site', _taskData!.siteName!), + if (_taskData?.buildingName != null) _buildInfoRow('Building', _taskData!.buildingName!), + if (_taskData?.floorName != null) _buildInfoRow('Floor', _taskData!.floorName!), + if (_taskData?.departmentName != null) _buildInfoRow('Department', _taskData!.departmentName!), + if (_taskData?.roomName != null) _buildInfoRow('Room', _taskData!.roomName!), + if (_taskData?.locationDescription != null) _buildInfoRow('Location', _taskData!.locationDescription!), + // Timer display using AppTimer widget if (_timerStart != null && !isClosed) ...[ 12.height, - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColor.primary10.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColor.primary10), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Working Time:', - style: AppTextStyles.bodyText.copyWith( - fontWeight: FontWeight.w600, - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - Text( - _formatDuration(DateTime.now().difference(_timerStart!)), - style: AppTextStyles.heading4.copyWith( - color: AppColor.primary10, - fontWeight: FontWeight.bold, - ), - ), - ], + AppTimer( + label: 'Working Time', + timer: TimerModel( + startAt: _timerStart, + endAt: null, + durationInSecond: 0, ), + enabled: false, // Disable controls, just show the timer + showTimer: true, ), ], ], @@ -270,15 +271,35 @@ class _OneTimeTaskViewState extends State { } List _convertInstructionsToChecklist() { - return _taskData?.instructions?.map((instruction) { - return ChecklistItem( - id: instruction.id ?? 0, - title: instruction.instructionText ?? '', - isChecked: instruction.isDone ?? false, - isMandatory: instruction.isRequired ?? false, - comment: instruction.comment ?? '', - ); - }).toList() ?? []; + // If checklist items are already created, reuse them + if (_checklistItems != null) { + // Update the existing items with latest data from instructions + for (var item in _checklistItems!) { + final instruction = _taskData?.instructions?.firstWhere((i) => i.id == item.id, orElse: () => Instruction()); + if (instruction?.id != null) { + item.isChecked = instruction!.isDone ?? false; + // Only update comment if it's different to avoid cursor jumping + if (item.comment != (instruction.comment ?? '')) { + item.comment = instruction.comment ?? ''; + } + } + } + return _checklistItems!; + } + + // Create new checklist items only on first call + _checklistItems = _taskData?.instructions?.map((instruction) { + return ChecklistItem( + id: instruction.id ?? 0, + title: instruction.instructionText ?? '', + isChecked: instruction.isDone ?? false, + isMandatory: instruction.isRequired ?? false, + comment: instruction.comment ?? '', + ); + }).toList() ?? + []; + + return _checklistItems!; } void _updateInstructions(List checklist) { @@ -295,21 +316,24 @@ class _OneTimeTaskViewState extends State { } void _onComplete() { - // Validate mandatory items - final uncheckedMandatory = _taskData?.instructions?.where((item) => - (item.isRequired ?? false) && !(item.isDone ?? false) - ).toList() ?? []; + // Validate mandatory checklist items + final uncheckedMandatory = _taskData?.instructions?.where((item) => (item.isRequired ?? false) && !(item.isDone ?? false)).toList() ?? []; if (uncheckedMandatory.isNotEmpty) { _showErrorDialog(context, "Please complete all mandatory tasks (marked with *)"); return; } - // Show preview dialog - _showPreviewDialog(); - } + // Validate that at least one image is captured + final capturedImages = _taskData?.imageRooms ?? []; + print('DEBUG: Captured images count: ${capturedImages.length}'); + print('DEBUG: Images: $capturedImages'); + + if (capturedImages.isEmpty) { + _showErrorDialog(context, "Please capture at least one image before completing"); + return; + } - void _showPreviewDialog() { // Calculate working hours double totalWorkingHours = 0; if (_timerStart != null) { @@ -317,28 +341,21 @@ class _OneTimeTaskViewState extends State { totalWorkingHours = duration.inSeconds / 3600.0; } - CleanerPreviewDialog.show( - context, - model: _convertToRecurrentWoData(), - totalWorkingHours: totalWorkingHours, - onConfirm: _performComplete, + // Navigate to preview screen + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => OneTimeTaskPreviewScreen( + taskData: _taskData!, + totalWorkingHours: totalWorkingHours, + onConfirm: () { + Navigator.pop(context); // Close preview screen + _performComplete(); // Proceed with completion + }, + ), + ), ); } - // Convert OneTimeTaskData to RecurrentWoData for preview dialog compatibility - dynamic _convertToRecurrentWoData() { - return { - 'siteName': _taskData?.siteName, - 'buildingName': _taskData?.buildingName, - 'floorName': _taskData?.floorName, - 'departmentName': _taskData?.departmentName, - 'roomName': _taskData?.roomName, - 'locationDescription': _taskData?.locationDescription, - 'taskNo': 'OT-${_taskData?.id}', - 'title': _taskData?.oneTimeSetting?.taskTypeName, - }; - } - Future _performComplete() async { showDialog( context: context, @@ -348,20 +365,33 @@ class _OneTimeTaskViewState extends State { try { final instructionsPayload = _taskData?.instructions?.map((instruction) { - return { - 'id': instruction.id, - 'instructionTextId': instruction.instructionTextId, - 'instructionText': instruction.instructionText, - 'isRequired': instruction.isRequired, - 'isDone': instruction.isDone, - 'comment': instruction.comment, - }; - }).toList() ?? []; + return { + 'id': instruction.id, + 'isDone': instruction.isDone, + 'comment': instruction.comment, + }; + }).toList() ?? + []; + + // Convert images to attachments format + final attachmentsPayload = _taskData?.imageRooms?.map((imageString) { + // imageString format: "filename|base64data" + final parts = imageString.split('|'); + final originalName = parts.isNotEmpty ? parts[0] : 'image.jpg'; + final base64Data = parts.length > 1 ? parts[1] : imageString; + + return { + 'id': 0, + 'name': "$originalName|$base64Data", // Send full formatted string + 'originalName': originalName, + }; + }).toList() ?? + []; final result = await CleanerProvider().completeOneTimeTask( id: _taskData!.id!, instructions: instructionsPayload, - imageRoom: _taskData?.imageRoom, + attachments: attachmentsPayload, ); if (mounted) Navigator.pop(context); // Close loading dialog @@ -387,20 +417,6 @@ class _OneTimeTaskViewState extends State { } } - void _showExitDialog() { - showDialog( - context: context, - builder: (context) => ConfirmDialog( - message: 'Do you want to exit without completing?', - title: 'Confirm', - onTap: () { - Navigator.pop(context); // Close dialog - Navigator.pop(context); // Exit screen - }, - ), - ); - } - void _showErrorDialog(BuildContext context, String message) { showDialog( context: context, @@ -412,26 +428,14 @@ class _OneTimeTaskViewState extends State { ); } - Color _getStatusColor() { - if (_taskData?.statusValue == 2) { - return Colors.green; - } - return AppColor.primary10; - } - - String _formatDate(String dateStr) { - try { - final date = DateTime.parse(dateStr); - return '${date.day}/${date.month}/${date.year}'; - } catch (e) { - return dateStr; + @override + void dispose() { + // Dispose all checklist item controllers + if (_checklistItems != null) { + for (var item in _checklistItems!) { + item.dispose(); + } } - } - - String _formatDuration(Duration duration) { - final hours = duration.inHours; - final minutes = duration.inMinutes.remainder(60); - final seconds = duration.inSeconds.remainder(60); - return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + super.dispose(); } } diff --git a/lib/modules/cleaner_module/provider/cleaner_provider.dart b/lib/modules/cleaner_module/provider/cleaner_provider.dart index 18a442b3..ecae5532 100644 --- a/lib/modules/cleaner_module/provider/cleaner_provider.dart +++ b/lib/modules/cleaner_module/provider/cleaner_provider.dart @@ -308,7 +308,7 @@ class CleanerProvider extends ChangeNotifier { Response response; try { response = await ApiManager.instance.get( - '${URLs.getRequestById}/$id', + '${URLs.getRequestById}?id=$id', ); // Parse the response body @@ -336,6 +336,7 @@ class CleanerProvider extends ChangeNotifier { "data": null, "message": "Failed to retrieve task. Please check your connection and try again.", }; + } } @@ -343,7 +344,7 @@ class CleanerProvider extends ChangeNotifier { Future> completeOneTimeTask({ required int id, required List> instructions, - String? imageRoom, + List>? attachments, }) async { Response response; try { @@ -352,7 +353,7 @@ class CleanerProvider extends ChangeNotifier { body: { "id": id, "instructions": instructions, - "imageRoom": imageRoom, + "attachments": attachments ?? [], }, showToast: false, ); diff --git a/lib/modules/cleaner_module/provider/fms_building_provider.dart b/lib/modules/cleaner_module/provider/fms_building_provider.dart deleted file mode 100644 index 39b3f0b3..00000000 --- a/lib/modules/cleaner_module/provider/fms_building_provider.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/cupertino.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/models/new_models/building.dart'; - -class FmsBuildingProvider extends ChangeNotifier { - //reset provider data - void reset() { - _buildings = null; - _stateCode = null; - } - - // state code of current request to defied error message - int? _stateCode; - int? get stateCode => _stateCode; - - // contain buildings data - List? _buildings; - List? get buildings => _buildings; - - // loading state - bool _loading = false; - bool get isLoading => _loading; - - set isLoading(bool isLoading) { - _loading = isLoading; - notifyListeners(); - } - - /// Get buildings by site ID - Future getBuildingsBySiteId(int siteId) async { - if (_loading == true) return -2; - _loading = true; - notifyListeners(); - Response response; - try { - response = await ApiManager.instance.post( - URLs.getCustomerBuildings, - body: { - "siteId": siteId, - }, - ); - _stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - // client's request was successfully received - List buildingsListJson = json.decode(response.body)["data"]; - _buildings = buildingsListJson.map((building) => Building.fromJson(building)).toList(); - } - _loading = false; - notifyListeners(); - return response.statusCode; - } catch (error) { - _loading = false; - _stateCode = -1; - notifyListeners(); - return -1; - } - } - - /// Get buildings list directly without provider state - Future> getBuildingsListBySiteId(int siteId) async { - Response response; - try { - response = await ApiManager.instance.post( - URLs.getCustomerBuildings, - body: { - "siteId": siteId, - }, - ); - - if (response.statusCode >= 200 && response.statusCode < 300) { - List buildingsListJson = json.decode(response.body)["data"]; - return buildingsListJson.map((building) => Building.fromJson(building)).toList(); - } - return []; - } catch (error) { - return []; - } - } -} diff --git a/lib/modules/cleaner_module/provider/fms_floor_provider.dart b/lib/modules/cleaner_module/provider/fms_floor_provider.dart deleted file mode 100644 index d3700761..00000000 --- a/lib/modules/cleaner_module/provider/fms_floor_provider.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/cupertino.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/models/new_models/floor.dart'; - -class FmsFloorProvider extends ChangeNotifier { - //reset provider data - void reset() { - _floors = null; - _stateCode = null; - } - - // state code of current request to defied error message - int? _stateCode; - int? get stateCode => _stateCode; - - // contain floors data - List? _floors; - List? get floors => _floors; - - // loading state - bool _loading = false; - bool get isLoading => _loading; - - set isLoading(bool isLoading) { - _loading = isLoading; - notifyListeners(); - } - - /// Get floors by site and building ID - Future getFloorsBySiteAndBuilding(int siteId, int buildingId) async { - if (_loading == true) return -2; - _loading = true; - notifyListeners(); - Response response; - try { - response = await ApiManager.instance.post( - URLs.getCustomerFloors, - body: { - "siteId": siteId, - "buildingId": buildingId, - }, - ); - _stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - // client's request was successfully received - List floorsListJson = json.decode(response.body)["data"]; - _floors = floorsListJson.map((floor) => Floor.fromJson(floor)).toList(); - } - _loading = false; - notifyListeners(); - return response.statusCode; - } catch (error) { - _loading = false; - _stateCode = -1; - notifyListeners(); - return -1; - } - } - - /// Get floors list directly without provider state - Future> getFloorsListBySiteAndBuilding(int siteId, int buildingId) async { - Response response; - try { - response = await ApiManager.instance.post( - URLs.getCustomerFloors, - body: { - "siteId": siteId, - "buildingId": buildingId, - }, - ); - - if (response.statusCode >= 200 && response.statusCode < 300) { - List floorsListJson = json.decode(response.body)["data"]; - return floorsListJson.map((floor) => Floor.fromJson(floor)).toList(); - } - return []; - } catch (error) { - return []; - } - } -} diff --git a/lib/modules/cleaner_module/provider/fms_location_description_provider.dart b/lib/modules/cleaner_module/provider/fms_location_description_provider.dart deleted file mode 100644 index db799dc4..00000000 --- a/lib/modules/cleaner_module/provider/fms_location_description_provider.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/cupertino.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/models/lookup.dart'; - -class LocationDescriptionResponse { - List? locationDescriptions; - List? deptRoom; - - LocationDescriptionResponse({ - this.locationDescriptions, - this.deptRoom, - }); - - factory LocationDescriptionResponse.fromJson(Map json) { - return LocationDescriptionResponse( - locationDescriptions: json['locationDescriptions'] != null - ? (json['locationDescriptions'] as List) - .map((e) => Lookup(name: e['locationDescriptionName'])) - .toList() - : null, - deptRoom: json['deptRoom'] != null - ? (json['deptRoom'] as List) - .map((e) => DepartmentRoomItem.fromJson(e)) - .toList() - : null, - ); - } -} - -class DepartmentRoomItem { - String? locationDescriptionName; - String? deptAndRoomName; - String? deptName; - String? roomName; - int? deptId; - int? roomId; - - DepartmentRoomItem({ - this.locationDescriptionName, - this.deptAndRoomName, - this.deptName, - this.roomName, - this.deptId, - this.roomId, - }); - - factory DepartmentRoomItem.fromJson(Map json) { - return DepartmentRoomItem( - locationDescriptionName: json['locationDescriptionName'], - deptAndRoomName: json['deptAndRoomName'], - deptName: json['deptName'], - roomName: json['roomName'], - deptId: json['deptId'], - roomId: json['roomId'], - ); - } -} - -class FmsLocationDescriptionProvider extends ChangeNotifier { - //reset provider data - void reset() { - _locationDescriptions = null; - _deptRoomList = null; - _stateCode = null; - } - - // state code of current request to defied error message - int? _stateCode; - int? get stateCode => _stateCode; - - // contain location descriptions data - List? _locationDescriptions; - List? get locationDescriptions => _locationDescriptions; - - // contain dept room list - List? _deptRoomList; - List? get deptRoomList => _deptRoomList; - - // loading state - bool _loading = false; - bool get isLoading => _loading; - - set isLoading(bool isLoading) { - _loading = isLoading; - notifyListeners(); - } - - /// Get location descriptions by site, building, and floor ID - Future getLocationDescriptions(int siteId, int buildingId, int floorId) async { - if (_loading == true) return -2; - _loading = true; - notifyListeners(); - Response response; - try { - response = await ApiManager.instance.get( - "${URLs.getCustomerLocationDescription}?siteId=$siteId&buildingId=$buildingId&floorId=$floorId", - enableToastMessage: false, - ); - _stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - // client's request was successfully received - final jsonData = json.decode(response.body)["data"]; - LocationDescriptionResponse locationResponse = LocationDescriptionResponse.fromJson(jsonData); - _locationDescriptions = locationResponse.locationDescriptions; - _deptRoomList = locationResponse.deptRoom; - } - _loading = false; - notifyListeners(); - return response.statusCode; - } catch (error) { - _loading = false; - _stateCode = -1; - notifyListeners(); - return -1; - } - } - - /// Get location descriptions and dept room list directly without provider state - Future getLocationDescriptionsList(int siteId, int buildingId, int floorId) async { - Response response; - try { - response = await ApiManager.instance.get( - "${URLs.getCustomerLocationDescription}?siteId=$siteId&buildingId=$buildingId&floorId=$floorId", - enableToastMessage: false, - ); - - if (response.statusCode >= 200 && response.statusCode < 300) { - final jsonData = json.decode(response.body)["data"]; - return LocationDescriptionResponse.fromJson(jsonData); - } - return null; - } catch (error) { - return null; - } - } -} diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 9ec79ab6..804372c1 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -1,10 +1,6 @@ import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.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'; @@ -13,15 +9,24 @@ import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; class CleanerChecklistWidget extends StatefulWidget { final List checklistItems; final Function(List)? onChecklistChanged; - final String? capturedImageBase64; - final Function(String?)? onImageCaptured; - bool isClosed; + final List capturedImages; + final Function(List)? onImagesChanged; + final bool isClosed; - CleanerChecklistWidget({Key? key, required this.checklistItems, this.onChecklistChanged, this.capturedImageBase64, this.onImageCaptured, this.isClosed = false}) : super(key: key); + const CleanerChecklistWidget({ + Key? key, + required this.checklistItems, + this.onChecklistChanged, + required this.capturedImages, + this.onImagesChanged, + this.isClosed = false + }) : super(key: key); @override State createState() => _CleanerChecklistWidgetState(); @@ -191,11 +196,23 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Captured Image'.addTranslation, - style: AppTextStyles.heading6.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Captured Image'.addTranslation, + style: AppTextStyles.heading6.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + // Text( + // '(${widget.capturedImages.length}/$maxImages)', + // style: AppTextStyles.bodyText.copyWith( + // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + // fontWeight: FontWeight.w600, + // ), + // ), + ], ), 8.height, _buildImageWidget(context), @@ -203,205 +220,148 @@ class _CleanerChecklistWidgetState extends State { ).toShadowContainer(context); } + static const int maxImages = 2; + /// Build the image widget with loading indicator for network images Widget _buildImageWidget(BuildContext context) { - if (widget.capturedImageBase64 == null || widget.capturedImageBase64!.isEmpty) { - // Show camera icon when no image - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.camera_alt, - size: 48, - color: context.isDark ? AppColor.white10 : AppColor.black10, - ), - ).onPress(() async { - final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera, imageQuality: 70, maxWidth: 800, maxHeight: 800); - if (pickedFile != null) { - // Convert image to Base64 with filename prefix (same format as CM request) - final bytes = await pickedFile.readAsBytes(); - final base64Image = base64Encode(bytes); - final fileName = pickedFile.path.split("/").last; - final formattedImage = "$fileName|$base64Image"; - - // Update parent with captured image - widget.onImageCaptured?.call(formattedImage); - } - }); - } + if (widget.capturedImages.isEmpty) { + // Show add image button when no image + return GestureDetector( + onTap: widget.isClosed ? null : () async { + // Only allow camera - pick one image at a time + final pickedFile = await ImagePicker().pickImage( + source: ImageSource.camera, + imageQuality: 70, + maxWidth: 800, + maxHeight: 800, + ); - Widget imageWidget; + if (pickedFile != null) { + final bytes = await pickedFile.readAsBytes(); + final base64Image = base64Encode(bytes); + final fileName = pickedFile.path.split("/").last; + final formattedImage = "$fileName|$base64Image"; - final imageString = widget.capturedImageBase64!; + // Add to existing images (max 2) + final updatedImages = List.from(widget.capturedImages); + updatedImages.add(formattedImage); - // First check if it's base64 encoded (contains | separator) - if (imageString.contains('|')) { - // Format: "filename|base64data" - try { - final base64Data = imageString.split('|').last; - imageWidget = Image.memory(base64Decode(base64Data), fit: BoxFit.cover); - return Stack( - alignment: AlignmentDirectional.topEnd, - children: [ - Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget) - .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); - }), - _delete(), - ], - ); - } catch (e) { - debugPrint('Error decoding base64 image: $e'); - return _buildCameraIcon(context); - } - } - - // Check if it's a local file path - if (_isLocalUrl(imageString)) { - try { - imageWidget = Image.file(File(imageString), fit: BoxFit.cover); - return Stack( - alignment: AlignmentDirectional.topEnd, - children: [ - Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget) - .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); - }), - _delete(), - ], - ); - } catch (e) { - debugPrint('Error loading local file: $e'); - return _buildCameraIcon(context); - } - } - - // If not local and not base64, it's a server filename - use NetworkImage with loading indicator - final imageUrl = URLs.getFileUrl(imageString); - if (imageUrl != null) { - imageWidget = Image.network( - imageUrl, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; + widget.onImagesChanged?.call(updatedImages); } - return Center( - child: CircularProgressIndicator( - color: AppColor.primary10, - value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, - ), - ); - }, - errorBuilder: (context, error, stackTrace) { - debugPrint('Error loading network image: $error'); - return _buildCameraIcon(context); }, - ); - return Stack( - alignment: AlignmentDirectional.topEnd, - children: [ - Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget) - .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), + child: Container( + height: 120, + width: 120, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: context.isDark ? AppColor.neutral20 : AppColor.neutral30, + width: 2, + style: BorderStyle.solid, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_photo_alternate, + size: 32, + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + 4.height, + Text( + 'Add Image', + style: AppTextStyles.tinyFont.copyWith( + color: context.isDark ? AppColor.white10 : AppColor.black10, ), ), - ); - }), - _delete(), - ], + ], + ), + ), ); } - return _buildCameraIcon(context); - } + // Check if images are URLs from API (like ServiceRequestDetailView does) + final bool hasApiImages = widget.capturedImages.any((img) => + img.startsWith('http://') || img.startsWith('https://')); - /// Build camera icon widget - Widget _buildCameraIcon(BuildContext context) { - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.camera_alt, - size: 48, - color: context.isDark ? AppColor.white10 : AppColor.black10, - ), + // If images are from API (URLs), use FilesList widget like ServiceRequestDetailView + if (hasApiImages && widget.isClosed) { + return FilesList( + images: widget.capturedImages, + showAsListView: false, + ); + } + + // Otherwise, use MultiImagePicker for base64 images or when editing + return MultiImagePicker( + imageUrls: widget.capturedImages, + enabled: !widget.isClosed, + maxImages: 2, // Enforce max 2 images + onImageTap: (index, imageUrl) { + // Handle image tap - show in full screen + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + backgroundColor: Colors.black, + body: SafeArea( + child: Stack( + children: [ + InteractiveViewer( + child: _buildImageForViewing(imageUrl), + ).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }, + onDelete: (index) { + // Handle image delete + if (index >= 0) { + setState(() { + widget.capturedImages.removeAt(index); + }); + widget.onImagesChanged?.call(widget.capturedImages); + } + }, + onAdd: (imageUrl) { + // Handle new image addition + setState(() { + widget.capturedImages.add(imageUrl); + }); + widget.onImagesChanged?.call(widget.capturedImages); + }, ); } - /// Check if the URL is a local file path - /// Same logic as multi_image_picker_item.dart - bool _isLocalUrl(String url) { - if (url.isEmpty) return false; - return url.startsWith("/") || url.startsWith("file://") || (url.length > 1 && url.substring(1).startsWith(':\\')); + /// Build image widget for viewing based on source type + Widget _buildImageForViewing(String imageUrl) { + try { + // Check if it's a URL from API + if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { + return Image.network( + imageUrl, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.broken_image, size: 48, color: Colors.white); + }, + ); + } + + // Otherwise, it's base64 encoded + final parts = imageUrl.split('|'); + final base64Data = parts.length > 1 ? parts[1] : imageUrl; + return Image.memory( + base64Decode(base64Data), + fit: BoxFit.contain, + ); + } catch (e) { + return const Icon(Icons.broken_image, size: 48, color: Colors.white); + } } @override @@ -409,26 +369,6 @@ class _CleanerChecklistWidgetState extends State { // Note: Don't dispose controllers here as they're managed by ChecklistItem super.dispose(); } - - Widget _delete() { - if (widget.isClosed) return const SizedBox(); - return Container( - height: 45.toScreenHeight, - width: 45.toScreenHeight, - margin: const EdgeInsets.only(top: 4, right: 4), - decoration: BoxDecoration( - color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.4), - border: Border.all( - color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.7), - ), - borderRadius: BorderRadius.circular(30), - //boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], - ), - child: const Icon(Icons.delete_rounded, color: Color(0xffF63939))) - .onPress(() { - widget.onImageCaptured?.call(null); - }); - } } /// Model class for checklist items @@ -446,10 +386,7 @@ class ChecklistItem { required this.isChecked, required this.comment, this.isMandatory = false, - }) : commentController = TextEditingController(text: comment) { - // Update the controller text whenever comment changes - commentController.text = comment; - } + }) : commentController = TextEditingController(text: comment); // Convert to JSON for API submission Map toJson() { diff --git a/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart b/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart deleted file mode 100644 index 6abd4d04..00000000 --- a/lib/modules/cleaner_module/widgets/cleaner_preview_dialog.dart +++ /dev/null @@ -1,457 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:flutter/material.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/widget_extensions.dart'; -import 'package:test_sa/extensions/text_extensions.dart'; -import 'package:test_sa/models/ppm/recurrent_wo.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/views/widgets/buttons/app_back_button.dart'; -import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; -import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; - -class CleanerPreviewDialog extends StatelessWidget { - final RecurrentWoData model; - final double totalWorkingHours; - final VoidCallback onConfirm; - - const CleanerPreviewDialog({ - Key? key, - required this.model, - required this.totalWorkingHours, - required this.onConfirm, - }) : super(key: key); - - static void show( - BuildContext context, { - required RecurrentWoData model, - required double totalWorkingHours, - required VoidCallback onConfirm, - }) { - showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) => CleanerPreviewDialog( - model: model, - totalWorkingHours: totalWorkingHours, - onConfirm: onConfirm, - ), - ); - } - - @override - Widget build(BuildContext context) { - return Dialog( - backgroundColor: Colors.transparent, - insetPadding: const EdgeInsets.symmetric(horizontal: 16), - child: Container( - constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.85), - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(14), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header matching DefaultAppBar style - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(14), - topRight: Radius.circular(14), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: 'Confirm Details'.heading3(context).custom( - fontWeight: FontWeight.w500, - ), - ), - Icon( - Icons.close, - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - size: 24, - ).onPress(() => Navigator.pop(context)), - ], - ), - ), - Divider(height: 1, color: context.isDark ? AppColor.neutral60 : AppColor.neutral100), - // Content - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildPreviewSection(context, 'Task Information', [ - _buildPreviewRow(context, context.translation.title, model.title ?? '-'), - _buildPreviewRow(context, context.translation.taskNo, model.taskNo ?? '-'), - _buildPreviewRow(context, context.translation.site, model.site?.siteName ?? '-'), - _buildPreviewRow(context, context.translation.department, model.department?.name ?? '-'), - _buildPreviewRow(context, context.translation.floor, model.floor?.name ?? '-'), - _buildPreviewRow(context, context.translation.room, model.room?.name ?? '-'), - _buildPreviewRow(context, context.translation.assignedEmployee, model.engineer?.userName ?? '-'), - ]), - 16.height, - // Total Working Time Section - Using WorkingTimeTile design - _buildPreviewSection(context, 'Total Working Time', [ - _buildWorkingTimeTile(context), - ]), - 16.height, - if (model.planRecurrentTaskCheckLists != null && model.planRecurrentTaskCheckLists!.isNotEmpty) ...[ - _buildPreviewSection(context, 'Checklist', [ - ...model.planRecurrentTaskCheckLists! - .map( - (item) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - item.isChecked ? Icons.check_circle : Icons.circle_outlined, - color: item.isChecked ? AppColor.primary10 : AppColor.neutral120, - size: 20, - ), - 8.width, - RichText( - text: TextSpan( - children: [ - TextSpan( - text: item.title, - style: AppTextStyles.bodyText2.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - if (item.isMandatory) - TextSpan( - text: ' *', - style: AppTextStyles.bodyText2.copyWith( - color: Colors.red, - ), - ), - ], - ), - ).expanded, - ], - ), - // Show comment if item is checked and has a comment - if (item.isChecked && item.comment.isNotEmpty == true) ...[ - 8.height, - Container( - width: double.infinity, - padding: const EdgeInsets.all(8), - margin: const EdgeInsets.only(left: 28), - decoration: BoxDecoration( - color: context.isDark ? AppColor.neutral60.withValues(alpha: 0.3) : AppColor.neutral100.withValues(alpha: 0.5), - borderRadius: BorderRadius.circular(6), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 'Comment:'.bodyText2(context).custom( - fontWeight: FontWeight.w600, - color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, - ), - 4.height, - item.comment.bodyText2(context).custom( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ], - ), - ), - ], - ], - ), - ), - ) - .toList(), - ]), - 16.height, - ], - if (model.comment != null && model.comment!.isNotEmpty) ...[ - _buildPreviewSection(context, context.translation.comment, [ - model.comment!.bodyText(context), - ]), - 16.height, - ], - if (model.imageRoom != null && model.imageRoom!.isNotEmpty) ...[ - _buildPreviewSection(context, 'Captured Image', [ - _buildImagePreview(context, model.imageRoom!), - ]), - 16.height, - ], - ], - ), - ), - ), - // Footer with Confirm button - Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - border: Border(top: BorderSide(color: context.isDark ? AppColor.neutral60 : AppColor.neutral100)), - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(14), - bottomRight: Radius.circular(14), - ), - ), - child: AppFilledButton( - label: context.translation.complete, - buttonColor: AppColor.primary10, - onPressed: () { - Navigator.pop(context); - onConfirm(); - }, - ), - ), - ], - ), - ), - ); - } - - Widget _buildPreviewSection(BuildContext context, String title, List children) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title.heading6(context).custom(fontWeight: FontWeight.w600), - 8.height, - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: children, - ).toShadowContainer(context, padding: 12), - ], - ); - } - - Widget _buildPreviewRow(BuildContext context, String label, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 2, - child: '$label:'.bodyText2(context).custom(color: AppColor.neutral120), - ), - 8.width, - Expanded( - flex: 3, - child: value.bodyText2(context).custom(color: AppColor.neutral120), - ), - ], - ), - ); - } - - Widget _buildWorkingTimeTile(BuildContext context) { - // Create timer list from model data, same as in RecurrentTaskInfoWidget - final timerList = model.planRecurrentTaskTimers?.map((e) { - return TimerHistoryModel( - id: e.id, - startTime: e.startTime, - endTime: e.endTime, - workingHours: e.workingHours, - ); - }).toList() ?? []; - - return Container( - height: 56.toScreenHeight, - padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), - alignment: Alignment.centerLeft, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Total Working Time", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: context.isDark ? null : AppColor.neutral20, - fontWeight: FontWeight.w500, - ), - ), - Text( - " ${CMRequestUtils.formatTotalWorkingHours(totalWorkingHours)}", - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ).expanded, - 8.width, - const Icon(Icons.info_outline_rounded, color: AppColor.primary10), - ], - ), - ).onPress(() async { - await showModalBottomSheet( - context: context, - isDismissible: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - clipBehavior: Clip.antiAliasWithSaveLayer, - builder: (BuildContext context) => TotalWorkingTimeDetailBottomSheet(timerList: timerList), - ); - }); - } - - Widget _buildImagePreview(BuildContext context, String imageString) { - Widget imageWidget; - - // First check if it's base64 encoded (contains | separator) - if (imageString.contains('|')) { - // Format: "filename|base64data" - try { - final base64Data = imageString.split('|').last; - imageWidget = Image.memory(base64Decode(base64Data), fit: BoxFit.cover); - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget, - ).onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); - }); - } catch (e) { - debugPrint('Error decoding base64 image: $e'); - return _buildCameraIcon(context); - } - } - - // Check if it's a local file path - if (_isLocalUrl(imageString)) { - try { - imageWidget = Image.file(File(imageString), fit: BoxFit.cover); - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget, - ).onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); - }); - } catch (e) { - debugPrint('Error loading local file: $e'); - return _buildCameraIcon(context); - } - } - - // If not local and not base64, it's a server filename - use NetworkImage with loading indicator - final imageUrl = URLs.getFileUrl(imageString); - if (imageUrl != null) { - imageWidget = Image.network( - imageUrl, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return Center( - child: CircularProgressIndicator( - color: AppColor.primary10, - value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, - ), - ); - }, - errorBuilder: (context, error, stackTrace) { - debugPrint('Error loading network image: $error'); - return _buildCameraIcon(context); - }, - ); - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: imageWidget, - ).onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - )); - }); - } - - return _buildCameraIcon(context); - } - - /// Build camera icon widget for fallback - Widget _buildCameraIcon(BuildContext context) { - return Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.camera_alt, - size: 48, - color: context.isDark ? AppColor.white10 : AppColor.black10, - ), - ); - } - - /// Check if the URL is a local file path - bool _isLocalUrl(String url) { - return url.startsWith('/') || url.startsWith('file://') || (url.length > 1 && url[1] == ':'); - } -} diff --git a/lib/modules/cleaner_module/widgets/multi_image_picker.dart b/lib/modules/cleaner_module/widgets/multi_image_picker.dart new file mode 100644 index 00000000..002acbe4 --- /dev/null +++ b/lib/modules/cleaner_module/widgets/multi_image_picker.dart @@ -0,0 +1,218 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/material.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/widget_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; + +class MultiImagePicker extends StatelessWidget { + final List imageUrls; + final Function(int index, String imageUrl)? onImageTap; + final Function(int index)? onDelete; + final Function(String imageUrl)? onAdd; + final bool enabled; + final int maxImages; + + const MultiImagePicker({ + Key? key, + required this.imageUrls, + this.onImageTap, + this.onDelete, + this.onAdd, + this.enabled = true, + this.maxImages = 2, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Display existing images horizontally with add button + Row( + children: [ + // Display existing images + ...List.generate( + imageUrls.length, + (index) { + return Padding( + padding: EdgeInsets.only(right: 8.toScreenWidth), + child: _buildImageItem(context, index), + ); + }, + ), + + // Add image button (only show if under max limit) + if (enabled && imageUrls.length < maxImages) + _buildAddImageButton(context), + ], + ), + ], + ); + } + + Widget _buildImageItem(BuildContext context, int index) { + final imageUrl = imageUrls[index]; + + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Container( + height: 120, + width: 120, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: _getImageWidget(imageUrl), + ), + ).onPress(() { + if (onImageTap != null) { + onImageTap!(index, imageUrl); + } else { + _showFullImage(context, imageUrl); + } + }), + if (enabled) + Container( + height: 32, + width: 32, + margin: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.9), + border: Border.all( + color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.7), + ), + borderRadius: BorderRadius.circular(20), + ), + child: const Icon(Icons.close, color: Color(0xffF63939), size: 20), + ).onPress(() { + if (onDelete != null) { + onDelete!(index); + } + }), + ], + ); + } + + Widget _buildAddImageButton(BuildContext context) { + return GestureDetector( + onTap: () => _pickImage(context), + child: Container( + height: 120, + width: 120, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: context.isDark ? AppColor.neutral20 : AppColor.neutral30, + width: 2, + style: BorderStyle.solid, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_photo_alternate, + size: 32, + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + 4.height, + Text( + 'Add Image', + style: AppTextStyles.tinyFont.copyWith( + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + ), + ], + ), + ), + ); + } + + Widget _getImageWidget(String imageUrl) { + // Check if it's base64 encoded (contains | separator) + if (imageUrl.contains('|')) { + try { + final base64Data = imageUrl.split('|').last; + return Image.memory( + base64Decode(base64Data), + fit: BoxFit.cover, + ); + } catch (e) { + return const Icon(Icons.broken_image, size: 48); + } + } + + // Check if it's a local file path + if (CMRequestUtils.isLocalUrl(imageUrl)) { + return Image.file( + File(imageUrl), + fit: BoxFit.cover, + ); + } + + // Otherwise treat as network URL + return Image.network( + imageUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.broken_image, size: 48); + }, + ); + } + + void _pickImage(BuildContext context) async { + // Directly open camera - no bottom sheet + final ImagePicker picker = ImagePicker(); + + final XFile? pickedFile = await picker.pickImage( + source: ImageSource.camera, + imageQuality: 70, + maxWidth: 800, + maxHeight: 800, + ); + + if (pickedFile != null) { + final bytes = await pickedFile.readAsBytes(); + final base64Image = base64Encode(bytes); + final fileName = pickedFile.path.split("/").last; + final formattedImage = "$fileName|$base64Image"; + + // Call the onAdd callback to notify parent + if (onAdd != null) { + onAdd!(formattedImage); + } + } + } + + void _showFullImage(BuildContext context, String imageUrl) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + backgroundColor: Colors.black, + body: SafeArea( + child: Stack( + children: [ + Center( + child: InteractiveViewer( + child: _getImageWidget(imageUrl), + ), + ), + const ABackButton(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart b/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart new file mode 100644 index 00000000..9adfdd58 --- /dev/null +++ b/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart @@ -0,0 +1,137 @@ +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/cleaner_module/pages/one_time_task_view.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class OneTimeTaskItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const OneTimeTaskItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + 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 ?? context.translation.recurrentWo).heading5(context), + 8.height, + '${context.translation.taskNo}: ${requestData!.requestNo}'.bodyText(context), + if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), + if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), + if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), + if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), + if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(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(() { + // Navigate to editable view for in-progress tasks + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => OneTimeTaskView( + taskId: requestData!.id ?? 0, + initialData: null, + ), + ), + ); + }); + } + if (requestDetails != null) { + 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 ?? context.translation.recurrentWo).heading5(context), + 8.height, + '${context.translation.site}: ${requestDetails!.site}'.bodyText(context), + '${context.translation.taskNo}: ${requestDetails!.requestNo}'.bodyText(context), + if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), + if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), + if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), + if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), + if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(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 { + // Navigate to editable view for in-progress tasks + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => OneTimeTaskView( + taskId: requestDetails!.id ?? 0, + initialData: null, + ), + ), + ); + }); + } + return const SizedBox(); + } +} diff --git a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart index b350d626..0857e414 100644 --- a/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart +++ b/lib/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart @@ -6,8 +6,6 @@ import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/ppm/recurrent_wo.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; -import 'package:test_sa/modules/cleaner_module/widgets/cleaner_checklist_widget.dart'; -import 'package:test_sa/modules/cleaner_module/widgets/cleaner_preview_dialog.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'; @@ -35,21 +33,11 @@ class RecurrentWorkOrderView extends StatefulWidget { class _RecurrentWorkOrderViewState extends State { AllRequestsProvider? allRequestsProvider; - DateTime? _housekeeperTimerStart; @override void initState() { allRequestsProvider = Provider.of(context, listen: false); - Provider.of(context, listen: false).getRecurrentWoById(id: widget.taskId!).then((_) { - // Start timer automatically for housekeeper type - final provider = Provider.of(context, listen: false); - bool isHouseKeeper = provider.recurrentWoData?.planRecurrentTaskCheckLists != null && - provider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty; - - if (isHouseKeeper && provider.recurrentWoData?.status?.value != 1) { - _housekeeperTimerStart = DateTime.now(); - } - }); + Provider.of(context, listen: false).getRecurrentWoById(id: widget.taskId!); super.initState(); } @@ -64,14 +52,13 @@ class _RecurrentWorkOrderViewState extends State { Widget build(BuildContext context) { return Consumer(builder: (context, requestProvider, child) { isClosed = requestProvider.recurrentWoData?.status?.value == 1; - bool isHouseKeeper = requestProvider.recurrentWoData!.planRecurrentTaskCheckLists != null && requestProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty; return Scaffold( appBar: DefaultAppBar( title: context.translation.recurrentWo, onWillPopScope: isClosed ? null : () { - _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper); + _updateTask(context: context, status: 0); }, ), body: allRequestsProvider!.isLoading @@ -89,8 +76,8 @@ class _RecurrentWorkOrderViewState extends State { RecurrentTaskInfoWidget( isClosed: isClosed, model: requestProvider.recurrentWoData, - isHouseKeeper: isHouseKeeper, - timerStartTime: _housekeeperTimerStart, + isHouseKeeper: false, + timerStartTime: null, ), IgnorePointer( ignoring: isClosed, @@ -103,24 +90,6 @@ class _RecurrentWorkOrderViewState extends State { ) : const SizedBox(), ), - 16.height, - isHouseKeeper - ? CleanerChecklistWidget( - isClosed: isClosed, - checklistItems: requestProvider.recurrentWoData!.planRecurrentTaskCheckLists ?? [], - onChecklistChanged: (value) { - setState(() { - requestProvider.recurrentWoData!.planRecurrentTaskCheckLists = value; - }); - }, - capturedImageBase64: requestProvider.recurrentWoData!.imageRoom, - onImageCaptured: (imageBase64) { - setState(() { - requestProvider.recurrentWoData!.imageRoom = imageBase64; - }); - }, - ) - : const SizedBox(), ], ), ).expanded, @@ -134,13 +103,13 @@ class _RecurrentWorkOrderViewState extends State { label: context.translation.save, buttonColor: context.isDark ? AppColor.neutral20 : AppColor.white60, textColor: context.isDark ? Colors.white : AppColor.black10, - onPressed: () => _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper), + onPressed: () => _updateTask(context: context, status: 0), ).expanded, 12.width, AppFilledButton( label: context.translation.complete, buttonColor: AppColor.primary10, - onPressed: () => _updateTask(context: context, status: 1, isHouseKeeper: isHouseKeeper), + onPressed: () => _updateTask(context: context, status: 1), ).expanded, ], ), @@ -154,77 +123,45 @@ class _RecurrentWorkOrderViewState extends State { cxt: context, showPopUp: !isClosed, onSave: () { - _updateTask(context: context, status: 0, isHouseKeeper: isHouseKeeper); + _updateTask(context: context, status: 0); }); }); } - void _updateTask({required BuildContext context, required int status, required bool isHouseKeeper}) async { - AllRequestsProvider allRequestsProvider = Provider.of(context, listen: false); - - // Only validate checklist if it exists and status is complete - if (status == 1 && allRequestsProvider.recurrentWoData?.planRecurrentTaskCheckLists != null && allRequestsProvider.recurrentWoData!.planRecurrentTaskCheckLists!.isNotEmpty) { - // Validate mandatory checklist items only - if (!_validateChecklist(context, allRequestsProvider.recurrentWoData!)) { - return; - } - } - - // For housekeeper: show preview dialog before completing - if (isHouseKeeper && status == 1) { - _showPreviewDialog(context, allRequestsProvider); - return; - } - + void _updateTask({required BuildContext context, required int status}) async { // Proceed with update - _performUpdate(context: context, status: status, isHouseKeeper: isHouseKeeper); + _performUpdate(context: context, status: status); } - void _performUpdate({required BuildContext context, required int status, required bool isHouseKeeper}) async { + void _performUpdate({required BuildContext context, required int status}) async { AllRequestsProvider allRequestsProvider = Provider.of(context, listen: false); - if (validate(context: context, model: allRequestsProvider.recurrentWoData!, status: status, isHouseKeeper: isHouseKeeper)) { - // For housekeeper: automatically add timer data - if (isHouseKeeper && _housekeeperTimerStart != null) { - DateTime endTime = DateTime.now(); - int durationInSecond = endTime.difference(_housekeeperTimerStart!).inSeconds; + if (validate(context: context, model: allRequestsProvider.recurrentWoData!, status: status)) { + // Use existing timer logic + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; + if (allRequestsProvider.recurrentWoData?.recurrentWoTimePicker != null) { + int durationInSecond = allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt!.difference(allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!).inSeconds; allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( PlanRecurrentTaskTimers( id: 0, - startTime: _housekeeperTimerStart!.toIso8601String(), - endTime: endTime.toIso8601String(), + startTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!.toIso8601String(), + endTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt?.toIso8601String(), workingHours: ((durationInSecond) / 60 / 60), ), ); - } else { - // For non-housekeeper: use existing timer logic - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers = allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers ?? []; - - if (allRequestsProvider.recurrentWoData?.recurrentWoTimePicker != null) { - int durationInSecond = allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt!.difference(allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!).inSeconds; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( - PlanRecurrentTaskTimers( - id: 0, - startTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.startAt!.toIso8601String(), - endTime: allRequestsProvider.recurrentWoData!.recurrentWoTimePicker!.endAt?.toIso8601String(), - workingHours: ((durationInSecond) / 60 / 60), - ), - ); - } - allRequestsProvider.recurrentWoData?.timerModelList?.forEach((timer) { - int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; - allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( - PlanRecurrentTaskTimers( - id: 0, - startTime: timer.startAt!.toIso8601String(), - endTime: timer.endAt?.toIso8601String(), - workingHours: ((durationInSecond) / 60 / 60), - ), - ); - }); } + allRequestsProvider.recurrentWoData?.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.add( + PlanRecurrentTaskTimers( + id: 0, + startTime: timer.startAt!.toIso8601String(), + endTime: timer.endAt?.toIso8601String(), + workingHours: ((durationInSecond) / 60 / 60), + ), + ); + }); final uniqueTimersMap = {}; allRequestsProvider.recurrentWoData?.planRecurrentTaskTimers?.forEach((timer) { @@ -268,38 +205,6 @@ class _RecurrentWorkOrderViewState extends State { } } - void _showPreviewDialog(BuildContext context, AllRequestsProvider provider) { - final model = provider.recurrentWoData!; - - // Calculate total working time - double totalWorkingHours = 0; - final timers = model.planRecurrentTaskTimers ?? []; - totalWorkingHours = timers.fold(0.0, (sum, item) { - if (item.startTime == null || item.endTime == null) return sum; - try { - final start = DateTime.parse(item.startTime!); - final end = DateTime.parse(item.endTime!); - final diffInHours = end.difference(start).inSeconds / 3600.0; - return sum + diffInHours; - } catch (_) { - return sum; - } - }); - - // Add current timer if running - if (_housekeeperTimerStart != null) { - final currentDuration = DateTime.now().difference(_housekeeperTimerStart!).inSeconds / 3600.0; - totalWorkingHours += currentDuration; - } - - CleanerPreviewDialog.show( - context, - model: model, - totalWorkingHours: totalWorkingHours, - onConfirm: () => _performUpdate(context: context, status: 1, isHouseKeeper: true), - ); - } - void _showErrorDialog(BuildContext context, String message) { showDialog( context: context, @@ -312,32 +217,8 @@ class _RecurrentWorkOrderViewState extends State { } } -bool _validateChecklist(BuildContext context, RecurrentWoData model) { - // Check if all mandatory checklist items are checked - final uncheckedMandatory = model.planRecurrentTaskCheckLists!.where((item) => item.isMandatory && !item.isChecked).toList(); - - if (uncheckedMandatory.isNotEmpty) { - showDialog( - context: context, - builder: (context) => ConfirmDialog( - message: "Please complete all mandatory tasks (marked with *)", - title: 'Validation Error', - onTap: () => Navigator.pop(context), - ), - ); - return false; - } - - return true; -} - -bool validate({required BuildContext context, required RecurrentWoData model, required int status, required bool isHouseKeeper}) { - // Skip timer validation for housekeeper type - if (isHouseKeeper) { - return true; - } - - // Original timer validation logic for non-housekeeper types +bool validate({required BuildContext context, required RecurrentWoData model, required int status}) { + // Timer validation logic bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; if (isTimerPickerEnable) { diff --git a/lib/new_views/common_widgets/app_bottom_nav_bar.dart b/lib/new_views/common_widgets/app_bottom_nav_bar.dart index 611afb27..cc12846e 100644 --- a/lib/new_views/common_widgets/app_bottom_nav_bar.dart +++ b/lib/new_views/common_widgets/app_bottom_nav_bar.dart @@ -23,9 +23,9 @@ class AppBottomNavigationBar extends StatelessWidget { List navItems = [ NavItemModel(0, "overview", context.translation.overview), - NavItemModel(1, "request_icon", context.translation.workOrder), + if(!isHouseKeeper) NavItemModel(1, "request_icon", context.translation.workOrder), if (!commissioningEng && !isHouseKeeper) NavItemModel(2, "add_icon", context.translation.calendar, showLabel: false), - NavItemModel(3, "assets", context.translation.assets), + if(!isHouseKeeper) NavItemModel(3, "assets", context.translation.assets), NavItemModel(4, "contact", context.translation.contact), ]; diff --git a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart index bf3db783..1ad1297c 100644 --- a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart +++ b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart @@ -26,8 +26,6 @@ class RecurrentWoItemView extends StatelessWidget { @override Widget build(BuildContext context) { final user = Provider.of(context, listen: false).user; - final settingProvider = Provider.of(context, listen: false); - final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; if (requestData != null) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -61,16 +59,11 @@ class RecurrentWoItemView extends StatelessWidget { // '${context.translation.assetSN}: ${request.assetSN}'.bodyText(context),//todo ask ahmed // '${context.translation.code}: ${request.code}'.bodyText(context), '${context.translation.taskNo}: ${requestData!.requestNo}'.bodyText(context), - if(requestData?.siteName!=null) - '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), - if(requestData!.buildingName!=null) - '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), - if(requestData!.departmentName!=null) - '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), - if(requestData!.floorName!=null) - '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), - if(requestData!.roomName!=null) - '${context.translation.room}: ${requestData!.roomName}'.bodyText(context), + if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), + if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), + if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), + if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), + if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(context), 8.height, Row( mainAxisSize: MainAxisSize.min, @@ -85,19 +78,7 @@ class RecurrentWoItemView extends StatelessWidget { ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() { - if (isHouseKeeper && (requestData?.statusValue == 4)) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CleanerSearchDetailsView( - requestId: requestData!.id ?? 0, - taskResult: null, - ), - ), - ); - } else { - Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestData!.id))); - } + Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestData!.id))); }); } if (requestDetails != null) { @@ -152,17 +133,6 @@ class RecurrentWoItemView extends StatelessWidget { ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { - if (isHouseKeeper && (requestDetails?.statusValue == 4)) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CleanerSearchDetailsView( - requestId: requestDetails!.id ?? 0, - taskResult: null, - ), - ), - ); - } Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestDetails!.id))); }); } 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 a5fa049b..704836c4 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 @@ -2,6 +2,7 @@ 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/asset_delivery_module/pages/asset_delivery_item_view.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/one_time_task_item_view.dart'; import 'package:test_sa/modules/demo_module/demo_request_item_view.dart'; import 'package:test_sa/modules/incident_module/incident_item_view.dart'; import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; @@ -87,6 +88,8 @@ class RequestPaginatedListview extends StatelessWidget { return DemoRequestItemView(requestData: request); case 16: return IncidentItemView(requestData: request); + case 17: + return OneTimeTaskItemView(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 e0005278..158d822c 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 @@ -7,6 +7,7 @@ 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/asset_delivery_module/pages/asset_delivery_item_view.dart'; +import 'package:test_sa/modules/cleaner_module/widgets/one_time_task_item_view.dart'; import 'package:test_sa/modules/demo_module/demo_request_item_view.dart'; import 'package:test_sa/modules/incident_module/incident_item_view.dart'; import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; @@ -76,6 +77,8 @@ class RequestItemViewList extends StatelessWidget { return DemoRequestItemView(requestDetails: list[index]); case 16: return IncidentItemView(requestDetails: list[index]); + case 17: + return OneTimeTaskItemView(requestDetails: list[index]); default: Container( height: 100, From 7327cda1bd6570c6c13aa7732aece90e9ff1ce46 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:14:25 +0300 Subject: [PATCH 09/73] add duration for task when completed --- .../pages/one_time_task_view.dart | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index e8e1ce28..bfc8e6fe 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -112,6 +112,23 @@ class _OneTimeTaskViewState extends State { bool get isClosed => _taskData?.statusValue == 2; + String _formatDuration(Duration duration) { + if (duration.inDays > 0) { + final hours = duration.inHours % 24; + final minutes = duration.inMinutes % 60; + return '${duration.inDays}d ${hours}h ${minutes}m'; + } else if (duration.inHours > 0) { + final minutes = duration.inMinutes % 60; + final seconds = duration.inSeconds % 60; + return '${duration.inHours}h ${minutes}m ${seconds}s'; + } else if (duration.inMinutes > 0) { + final seconds = duration.inSeconds % 60; + return '${duration.inMinutes}m ${seconds}s'; + } else { + return '${duration.inSeconds}s'; + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -252,6 +269,16 @@ class _OneTimeTaskViewState extends State { showTimer: true, ), ], + // Display total duration if task is completed + if (isClosed && _taskData?.startTime != null && _taskData?.endTime != null) ...[ + // 12.height, + Text( + 'Total Duration: ${_formatDuration(DateTime.parse(_taskData!.endTime!).difference(DateTime.parse(_taskData!.startTime!)))}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ], ], ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), ], From 154982d9b0f5e88c7eabe2d7f3e3442e1b9065a0 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Sun, 7 Jun 2026 09:37:19 +0300 Subject: [PATCH 10/73] improvements --- .../pages/one_time_task_preview_screen.dart | 71 ++++++++++++++----- .../pages/one_time_task_view.dart | 4 ++ 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart index 18e4d0ad..0a6eeaf6 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -10,6 +10,9 @@ 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/controllers/api_routes/urls.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/models/timer_model.dart'; import 'dart:convert'; class OneTimeTaskPreviewScreen extends StatelessWidget { @@ -124,6 +127,16 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { } Widget _buildWorkingHoursCard(BuildContext context) { + // Parse timer start time from taskData if available + DateTime? timerStartTime; + if (taskData.startTime != null) { + try { + timerStartTime = DateTime.parse(taskData.startTime!); + } catch (e) { + print('Error parsing start time: $e'); + } + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -134,26 +147,50 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { fontWeight: FontWeight.w600, ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Total Working Hours', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + 12.height, + if (timerStartTime != null) + AppTimer( + label: 'Working Time', + timer: TimerModel( + startAt: timerStartTime, + endAt: null, + durationInSecond: 0, ), - Text( - '${totalWorkingHours.toStringAsFixed(2)} hrs', - style: AppTextStyles.heading6.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - fontWeight: FontWeight.w600, - ), + enabled: false, // Disable controls, just show the timer + showTimer: true, + decoration: BoxDecoration( + color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), ), - ], - ).paddingAll(12), + ) + else + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Working Hours', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + Text( + CMRequestUtils.formatTotalWorkingHours(totalWorkingHours), + style: AppTextStyles.heading6.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), ], - ).toShadowContainer(context, padding: 12); + ).toShadowContainer(context, padding: 16); } Widget _buildChecklistCard(BuildContext context) { diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index bfc8e6fe..d40921cb 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -267,6 +267,10 @@ class _OneTimeTaskViewState extends State { ), enabled: false, // Disable controls, just show the timer showTimer: true, + decoration: BoxDecoration( + color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), ), ], // Display total duration if task is completed From 850846f22706134ee87782d094ed36f7cc4e3544 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:25:36 +0300 Subject: [PATCH 11/73] traf request attachement added --- lib/controllers/api_routes/urls.dart | 8 ++--- .../traf_module/create_traf_request_page.dart | 35 ++++++++++++++++--- .../traf_module/traf_request_detail_page.dart | 16 +++++++++ .../traf_module/traf_request_model.dart | 4 +++ pubspec.lock | 32 ++++++++--------- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index d274eebf..cbfd5adc 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "40"; - static const host1 = "https://atomsm.hmg.com"; // production url + // 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://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 8a7f5435..206d1350 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; @@ -7,13 +10,13 @@ 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/device/asset.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/traf_department.dart'; import 'package:test_sa/models/new_models/users_based_on_search_model.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/traf_module/asset_auto_complete_field.dart'; -import 'package:test_sa/modules/traf_module/traf_request_detail_page.dart'; -import 'package:test_sa/modules/traf_module/update_traf_request_page.dart'; import 'package:test_sa/modules/traf_module/users_auto_complete_field.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -21,11 +24,11 @@ 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/new_views/common_widgets/single_item_drop_down_menu.dart'; -import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/lookups/department_lookup_provider.dart'; import 'package:test_sa/providers/lookups/request_type_lookup_provider.dart'; import 'package:test_sa/providers/lookups/yes_no_lookup_provider.dart'; import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'traf_request_model.dart'; import 'traf_request_provider.dart'; @@ -58,6 +61,7 @@ class _CreateTRAFRequestPageState extends State { List departments = []; List userBasedOnSearch = []; + List attachments = []; FocusNode otherServicesEffectsNode = FocusNode(); FocusNode useInCombinationNode = FocusNode(); @@ -443,9 +447,21 @@ class _CreateTRAFRequestPageState extends State { trafRequest?.usedWithCombination = value; }, ), - ] + ], + 12.height, + AttachmentPicker( + label: context.translation.attachFiles, + attachment: attachments, + buttonColor: AppColor.black10, + onlyImages: false, + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.neutral120), + onChange: (attachment) { + attachments = attachment; + }, + ), ], ).toShadowContainer(context, padding: 12, borderRadius: 20), + 16.height, Row( children: [ @@ -521,6 +537,17 @@ class _CreateTRAFRequestPageState extends State { return; } + // Process attachments similar to CreateCMRequest + trafRequest?.attachments = []; + for (var item in attachments) { + String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') + ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") + : item.name ?? ''; + trafRequest?.attachments!.add( + Attachments(id: 0, trafId: 0, attachmentName: fileName), + ); + } + Utils.showLoading(context); bool isSuccess = await Provider.of(context, listen: false).addTraf(trafRequest!.toJson()); Utils.hideLoading(context); diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 00f25512..e4a804c7 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/user_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/enums/user_types.dart'; @@ -12,9 +15,11 @@ import 'package:test_sa/modules/traf_module/update_traf_request_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/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'; +import '../../controllers/api_routes/urls.dart'; import 'asset_analytics_model.dart'; import 'traf_request_model.dart'; @@ -32,6 +37,8 @@ class TrafRequestDetailPage extends StatefulWidget { } class _TrafRequestDetailPageState extends State { + List _attachments = []; + @override void initState() { super.initState(); @@ -65,6 +72,15 @@ class _TrafRequestDetailPageState extends State { requestInformation(trafProvider.trafRequestDataModel!), 12.height, assetInformation(trafProvider.trafRequestDataModel!), + if (_attachments.isNotEmpty) ...[ + 12.height, + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 8.height, + FilesList(images: trafProvider.trafRequestDataModel?.attachments?.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + ], ], ).expanded, // // if (isEngineer) diff --git a/lib/modules/traf_module/traf_request_model.dart b/lib/modules/traf_module/traf_request_model.dart index af3977be..88295bdd 100644 --- a/lib/modules/traf_module/traf_request_model.dart +++ b/lib/modules/traf_module/traf_request_model.dart @@ -92,6 +92,7 @@ class TrafRequestDataModel { String? usedWithCombination; String? comment; List? attachments; + bool? isFromMobile; int? isBudgetId; String? isBudgetName; String? firstLineManagerId; @@ -189,6 +190,7 @@ class TrafRequestDataModel { this.isCombinationName, this.usedWithCombination, this.comment, + this.isFromMobile = true, this.attachments, this.isBudgetId, this.isBudgetName, @@ -302,6 +304,7 @@ class TrafRequestDataModel { isCombinationName = json['isCombinationName']; usedWithCombination = json['usedWithCombination']; comment = json['comment']; + isFromMobile = json['isFromMobile']; if (json['attachments'] != null) { attachments = []; json['attachments'].forEach((v) { @@ -418,6 +421,7 @@ class TrafRequestDataModel { data['isCombinationName'] = this.isCombinationName; data['usedWithCombination'] = this.usedWithCombination; data['comment'] = this.comment; + data['isFromMobile'] = this.isFromMobile; if (this.attachments != null) { data['attachments'] = this.attachments!.map((v) => v.toJson()).toList(); } else { diff --git a/pubspec.lock b/pubspec.lock index 5880d411..4f2f810a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -593,14 +593,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.0" - flutter_vibrate: - dependency: "direct main" - description: - name: flutter_vibrate - sha256: "6eea0e94dd7dc8c7872e994a1139d55a8640e32fd5898ff59ee8bd53b8dea3b3" - url: "https://pub.dev" - source: hosted - version: "1.4.0" flutter_web_plugins: dependency: transitive description: flutter @@ -718,6 +710,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + haptic_feedback: + dependency: "direct main" + description: + name: haptic_feedback + sha256: dcc2494994c41428823f8f2082fd17a4e89e372bd142e07681420cbfaf99dcad + url: "https://pub.dev" + source: hosted + version: "0.6.4+3" hooks: dependency: transitive description: @@ -1002,10 +1002,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -1074,10 +1074,10 @@ packages: dependency: "direct main" description: name: nfc_manager - sha256: "24c78b0e5702da53e7f8794d073624c0bee7cd99924f257cbd11f5d1c5866879" + sha256: "1b8d3788ed71bb34f59878a445e433891eb2268353289515c1542b2780151fe6" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.2.1" objective_c: dependency: transitive description: @@ -1719,10 +1719,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" timezone: dependency: transitive description: @@ -1940,5 +1940,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.5" + dart: ">=3.11.4 <4.0.0" + flutter: ">=3.38.4" From 2c7a8d035cc5fe56f38ce97f00052480c2622213 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:45:21 +0300 Subject: [PATCH 12/73] added new payload for adding traf as requested by backend and added some missing data in details page --- .../traf_module/create_traf_request_page.dart | 2 +- .../traf_module/traf_request_detail_page.dart | 30 ++++++--- .../traf_module/traf_request_model.dart | 65 +++++++++++++++++++ 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 206d1350..1cc00f1c 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -549,7 +549,7 @@ class _CreateTRAFRequestPageState extends State { } Utils.showLoading(context); - bool isSuccess = await Provider.of(context, listen: false).addTraf(trafRequest!.toJson()); + bool isSuccess = await Provider.of(context, listen: false).addTraf(trafRequest!.toJsonNew()); Utils.hideLoading(context); if (isSuccess) { Navigator.pop(context); diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index e4a804c7..e56b043a 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -42,7 +43,9 @@ class _TrafRequestDetailPageState extends State { @override void initState() { super.initState(); - Provider.of(context, listen: false).getTRAFById(widget.trafId); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of(context, listen: false).getTRAFById(widget.trafId); + }); } @override @@ -60,6 +63,9 @@ class _TrafRequestDetailPageState extends State { builder: (_, isLoading, __) { if (isLoading) return const ALoading(); TrafRequestProvider trafProvider = Provider.of(context, listen: false); + if (trafProvider.trafRequestDataModel?.attachments != null) { + _attachments = trafProvider.trafRequestDataModel!.attachments!.map((e) => File(URLs.getFileUrl(e.attachmentName ?? '') ?? '')).toList(); + } return trafProvider.trafRequestDataModel == null ? const NoDataFound().center : Column( @@ -72,15 +78,6 @@ class _TrafRequestDetailPageState extends State { requestInformation(trafProvider.trafRequestDataModel!), 12.height, assetInformation(trafProvider.trafRequestDataModel!), - if (_attachments.isNotEmpty) ...[ - 12.height, - Text( - "Attachments".addTranslation, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), - 8.height, - FilesList(images: trafProvider.trafRequestDataModel?.attachments?.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), - ], ], ).expanded, // // if (isEngineer) @@ -134,6 +131,10 @@ class _TrafRequestDetailPageState extends State { 8.height, 'TRAF No: ${data.reqCode ?? '-'}'.bodyText(context), 'Request type: ${data.requestTypeName ?? '-'}'.bodyText(context), + if (data.requestTypeValue == 1) ...[ + 4.height, + 'Requested Quantity: ${data.qty ?? '-'}'.bodyText(context), + ], 4.height, 'How would the requested technology solve the current situation and/or serve the purpose?: ${data.purposeAnswer ?? '-'}'.bodyText(context), 4.height, @@ -164,6 +165,15 @@ class _TrafRequestDetailPageState extends State { 4.height, 'Equipment going to be used with combination of other equipments:\n${data.usedWithCombination ?? '-'}'.bodyText(context), ], + if (_attachments.isNotEmpty) ...[ + 4.height, + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + // 8.height, + FilesList(images: _attachments.map((e) => URLs.getFileUrl(e.path ?? '') ?? '').toList() ?? []), + ], ], ).toShadowContainer(context, borderRadius: 20, padding: 12); } diff --git a/lib/modules/traf_module/traf_request_model.dart b/lib/modules/traf_module/traf_request_model.dart index 88295bdd..09079f3f 100644 --- a/lib/modules/traf_module/traf_request_model.dart +++ b/lib/modules/traf_module/traf_request_model.dart @@ -88,6 +88,7 @@ class TrafRequestDataModel { String? isEffectedName; String? effectedServices; int? isCombinationId; + int?requestTypeValue; String? isCombinationName; String? usedWithCombination; String? comment; @@ -187,6 +188,7 @@ class TrafRequestDataModel { this.isEffectedName, this.effectedServices, this.isCombinationId, + this.requestTypeValue, this.isCombinationName, this.usedWithCombination, this.comment, @@ -301,6 +303,7 @@ class TrafRequestDataModel { isEffectedName = json['isEffectedName']; effectedServices = json['effectedServices']; isCombinationId = json['isCombinationId']; + requestTypeValue = json['requestTypeValue']; isCombinationName = json['isCombinationName']; usedWithCombination = json['usedWithCombination']; comment = json['comment']; @@ -418,6 +421,7 @@ class TrafRequestDataModel { data['isEffectedName'] = this.isEffectedName; data['effectedServices'] = this.effectedServices; data['isCombinationId'] = this.isCombinationId; + data['requestTypeValue'] = this.requestTypeValue; data['isCombinationName'] = this.isCombinationName; data['usedWithCombination'] = this.usedWithCombination; data['comment'] = this.comment; @@ -478,6 +482,67 @@ class TrafRequestDataModel { data['modifiedDate'] = this.modifiedDate; return data; } + Map toJsonNew() { + final Map data = {}; + + data['id'] = id; + data['reqNo'] = reqNo; + data['reqCode'] = reqCode; + data['employeeId'] = employeeId; + data['positionId'] = positionId; + + data['departments'] = + departments?.map((v) => v.toJson()).toList() ?? []; + if (this.trafDepartments != null) { + data['trafDepartments'] = this.trafDepartments!.map((v) => v.toJson()).toList(); + } + + data['siteId'] = siteId; + data['siteName'] = siteName; + data['requesterExtensionNumber'] = requesterExtensionNumber; + data['requesterExtensionName'] = requesterExtensionName; + data['requesterExtensionPositionId'] = requesterExtensionPositionId; + + data['requestTypeId'] = requestTypeId; + + data['assetNDId'] = assetNDId; + data['assetId'] = assetId; + data['qty'] = qty; + + data['purposeAnswer'] = purposeAnswer; + data['currentPractise'] = currentPractise; + + data['censusQ1'] = censusQ1; + data['censusQ2'] = censusQ2; + data['censusQ3'] = censusQ3; + data['censusQ4'] = censusQ4; + + data['isFromMobile'] = isFromMobile; + + data['trafContacts'] = + trafContacts?.map((v) => v.toJson()).toList() ?? []; + + data['usingSolelyOrSharedId'] = usingSolelyOrSharedId; + + data['isEffectedId'] = isEffectedId; + data['effectedServices'] = effectedServices; + + data['isCombinationId'] = isCombinationId; + data['usedWithCombination'] = usedWithCombination; + + data['comment'] = comment; + + data['isBudgetId'] = isBudgetId; + + data['attachments'] = + attachments?.map((v) => v.toJson()).toList() ?? []; + + return data; + } + + + + } class TrafContacts { From f2b472f4df70ad7103da7f617196d2e2b12c2ca4 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:41:39 +0300 Subject: [PATCH 13/73] added identifier for loan request department model --- lib/models/new_models/traf_department.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/models/new_models/traf_department.dart b/lib/models/new_models/traf_department.dart index 5a2e256d..3ac9841c 100644 --- a/lib/models/new_models/traf_department.dart +++ b/lib/models/new_models/traf_department.dart @@ -3,9 +3,9 @@ import 'package:test_sa/models/new_models/room_model.dart'; import '../base.dart'; class TrafDepartment extends Base { - TrafDepartment({ - this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate - }) : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id + TrafDepartment( + {this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate}) + : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id TrafDepartment.fromJson(Map json) { departmentName = json['departmentName'] ?? json['name']; @@ -15,6 +15,7 @@ class TrafDepartment extends Base { costCenterName = json['costCenterName']; name = json['name']; id = json['id']; + identifier = id?.toString() ?? ''; createdBy = json['createdBy']; createdDate = json['createdDate']; modifiedBy = json['modifiedBy']; From 450cb91268b6cf1d31666e1b86829769d9c4e5e4 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:28:06 +0300 Subject: [PATCH 14/73] site deparment dropdown added for create traf request --- lib/controllers/api_routes/urls.dart | 1 + .../providers/api/all_requests_provider.dart | 13 ++- lib/main.dart | 2 + lib/models/new_models/traf_customer.dart | 62 +++++++++++ .../traf_module/create_traf_request_page.dart | 102 ++++++++++++++++-- .../traf_module/traf_request_model.dart | 3 +- .../lookups/customer_lookup_provider.dart | 70 ++++++++++++ 7 files changed, 241 insertions(+), 12 deletions(-) create mode 100644 lib/models/new_models/traf_customer.dart create mode 100644 lib/providers/lookups/customer_lookup_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index cbfd5adc..c063e1bc 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -354,6 +354,7 @@ class URLs { static get getSiteContactInfo => "$_baseUrl/AssetGroupSiteContactInfo"; // add static get getDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; // add + static get getCustomersByUserId => "$_baseUrl/TRAFDataSource/GetCustomersByUserId"; // add // asset inventory Urls. static get getAssetInventoryById => '$_baseUrl/AssetInventory/GetAssetInventoryById'; diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index de05fc92..e3374962 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -157,15 +157,20 @@ class AllRequestsProvider extends ChangeNotifier { } // switching asset inventory to users permissions + // context.userProvider.modulesPermissionsList.forEach((module) { + // if (module.module!.value! == 8 || module.module!.value! == 9) { + // list.add(module.module!.value!); + // } + // }); context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 8 || module.module!.value! == 9) { + if (module.module!.value! == 8) { list.add(module.module!.value!); } }); - // if (context.settingProvider.isUserFlowMedical && context.userProvider.isNurse) { - // list.add(9); // - // } + if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor) { + list.add(9); // + } list.add(12); if (context.userProvider.isCommissioningEngineer && context.settingProvider.isUserFlowMedical) { list = [13]; diff --git a/lib/main.dart b/lib/main.dart index fcb78f3e..878a0f53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -134,6 +134,7 @@ import 'modules/tm_module/device_transfer/device_search_screen.dart'; import 'modules/tm_module/tasks/create_task_view.dart'; import 'modules/tm_module/gas_refill/gas_refill_request_form.dart'; import 'providers/lookups/classification_lookup_provider.dart'; +import 'providers/lookups/customer_lookup_provider.dart'; import 'providers/lookups/department_lookup_provider.dart'; import 'providers/lookups/request_type_lookup_provider.dart'; import 'providers/lookups/yes_no_lookup_provider.dart'; @@ -230,6 +231,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => RequestTypeLookupProvider(), lazy: true), ChangeNotifierProvider(create: (_) => YesNoLookupProvider(), lazy: true), ChangeNotifierProvider(create: (_) => DepartmentLookupProvider(), lazy: true), + ChangeNotifierProvider(create: (_) => CustomerLookupProvider(), lazy: true), ChangeNotifierProvider(create: (_) => EndUserStatusLookupProvider(), lazy: true), ChangeNotifierProvider(create: (_) => EndUserRejectionReasonLookupProvider(), lazy: true), ChangeNotifierProvider(create: (_) => AttachmentTypeLookupProvider(), lazy: true), diff --git a/lib/models/new_models/traf_customer.dart b/lib/models/new_models/traf_customer.dart new file mode 100644 index 00000000..4987afa1 --- /dev/null +++ b/lib/models/new_models/traf_customer.dart @@ -0,0 +1,62 @@ +import '../base.dart'; + +class TrafCustomer extends Base { + int? customerId; + int? langId; + String? nameValue; + String? customerName; + String? languageName; + int? id; + String? createdBy; + String? createdDate; + String? modifiedBy; + String? modifiedDate; + + TrafCustomer({ + this.customerId, + this.langId, + this.nameValue, + this.customerName, + this.languageName, + this.id, + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate, + }) : super(identifier: id?.toString() ?? '', name: nameValue ?? customerName); + + factory TrafCustomer.fromJson(Map json) { + return TrafCustomer( + customerId: json['customerId'], + langId: json['langId'], + nameValue: json['nameValue'], + customerName: json['customerName'], + languageName: json['languageName'], + id: json['id'], + createdBy: json['createdBy'], + createdDate: json['createdDate'], + modifiedBy: json['modifiedBy'], + modifiedDate: json['modifiedDate'], + ); + } + + Map toJson() { + return { + 'customerId': customerId, + 'langId': langId, + 'nameValue': nameValue, + 'customerName': customerName, + 'languageName': languageName, + 'id': id, + 'createdBy': createdBy, + 'createdDate': createdDate, + 'modifiedBy': modifiedBy, + 'modifiedDate': modifiedDate, + }; + } + + @override + String toString() { + return nameValue ?? customerName ?? ''; + } +} diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 1cc00f1c..4535cdc9 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -12,6 +12,7 @@ import 'package:test_sa/helper/utils.dart'; import 'package:test_sa/models/device/asset.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/models/new_models/traf_customer.dart'; import 'package:test_sa/models/new_models/traf_department.dart'; import 'package:test_sa/models/new_models/users_based_on_search_model.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; @@ -24,6 +25,7 @@ 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/new_views/common_widgets/single_item_drop_down_menu.dart'; +import 'package:test_sa/providers/lookups/customer_lookup_provider.dart'; import 'package:test_sa/providers/lookups/department_lookup_provider.dart'; import 'package:test_sa/providers/lookups/request_type_lookup_provider.dart'; import 'package:test_sa/providers/lookups/yes_no_lookup_provider.dart'; @@ -55,6 +57,9 @@ class _CreateTRAFRequestPageState extends State { Lookup? otherServicesEffects; Lookup? useInCombination; + TrafCustomer? selectedSite; + TrafDepartment? selectedDepartment; + TrafRequestDataModel? trafRequest; List abc = []; @@ -66,17 +71,42 @@ class _CreateTRAFRequestPageState extends State { FocusNode otherServicesEffectsNode = FocusNode(); FocusNode useInCombinationNode = FocusNode(); FocusNode departmentNode = FocusNode(); + FocusNode siteDepartmentNode = FocusNode(); @override void initState() { super.initState(); trafRequest = TrafRequestDataModel(id: 0, attachments: [], departments: [], qty: 1); - resetProviders(); + // Reset providers and load site data after the first frame to avoid provider lifecycle issues + WidgetsBinding.instance.addPostFrameCallback((_) { + // Only reset providers if they don't have data (first time load) + // This prevents unnecessary loading when navigating back and forth + final requestTypeProvider = Provider.of(context, listen: false); + final yesNoProvider = Provider.of(context, listen: false); + final customerProvider = Provider.of(context, listen: false); + + // Only load if not already loaded + if (requestTypeProvider.items.isEmpty) { + requestTypeProvider.getData(); + } + + if (yesNoProvider.items.isEmpty) { + yesNoProvider.getData(); + } + + // Load customer/site data using the string user ID (GUID) only if not loaded + if (customerProvider.items.isEmpty) { + final userId = context.userProvider.user?.userID; + if (userId != null && userId.isNotEmpty) { + customerProvider.getDataByUserId(userId); + } + } + }); } void resetProviders() { Provider.of(context, listen: false).reset(); - // Provider.of(context, listen: false).reset(); + Provider.of(context, listen: false).reset(); } @override @@ -184,6 +214,52 @@ class _CreateTRAFRequestPageState extends State { style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, fontWeight: FontWeight.w600), ), 12.height, + SingleItemDropDownMenu( + context: context, + showAsBottomSheet: true, + backgroundColor: AppColor.neutral100, + showShadow: false, + title: "Site", + requestById: int.tryParse(context.userProvider.user?.userID ?? ''), + initialValue: selectedSite, + onSelect: (value) { + selectedSite = value; + trafRequest?.siteId = value?.customerId; + // Reset department when site changes + selectedDepartment = null; + trafRequest?.departmentId = null; + + // Reset and fetch department data based on selected site + Provider.of(context, listen: false).reset(); + if (selectedSite != null && selectedSite!.customerId != null) { + // Fetch departments for the selected site + Provider.of(context, listen: false).getData(id: selectedSite!.customerId); + siteDepartmentNode.requestFocus(); + } + setState(() {}); + }, + ), + 12.height, + Focus( + focusNode: siteDepartmentNode, + child: SingleItemDropDownMenu( + context: context, + showAsBottomSheet: true, + backgroundColor: AppColor.neutral100, + showShadow: false, + title: "Department", + requestById: null, + // Don't pass requestById to prevent auto-fetch + initialValue: selectedDepartment, + enabled: selectedSite != null, + onSelect: (value) { + selectedDepartment = value; + trafRequest?.departmentId = value?.id?.toString(); + setState(() {}); + }, + ), + ), + 12.height, AppTextFormField( initialValue: "", labelText: "How would the requested technology solve the current situation and/or serve the purpose?", @@ -461,7 +537,6 @@ class _CreateTRAFRequestPageState extends State { ), ], ).toShadowContainer(context, padding: 12, borderRadius: 20), - 16.height, Row( children: [ @@ -507,10 +582,25 @@ class _CreateTRAFRequestPageState extends State { "Pick asset".showToast; return; } + + // Validate site and department selection + if (selectedSite == null) { + "Please select a site".showToast; + return; + } + + if (selectedDepartment == null) { + "Please select a department".showToast; + return; + } + if (!_formKey.currentState!.validate()) return; trafRequest?.employeeId = context.userProvider.user?.userID; - trafRequest?.siteId = context.userProvider.user?.clientId; + // Set site and department IDs from selected dropdowns + trafRequest?.siteId = selectedSite?.customerId; + trafRequest?.departmentId = selectedDepartment?.id; + trafRequest?.trafContacts = []; trafRequest?.trafContacts = userBasedOnSearch.map((item) => TrafContacts(id: 0, trafId: 0, name: item.userName, userId: item.userId)).toList(); @@ -540,9 +630,7 @@ class _CreateTRAFRequestPageState extends State { // Process attachments similar to CreateCMRequest trafRequest?.attachments = []; for (var item in attachments) { - String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') - ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") - : item.name ?? ''; + String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; trafRequest?.attachments!.add( Attachments(id: 0, trafId: 0, attachmentName: fileName), ); diff --git a/lib/modules/traf_module/traf_request_model.dart b/lib/modules/traf_module/traf_request_model.dart index 09079f3f..a9d7c40e 100644 --- a/lib/modules/traf_module/traf_request_model.dart +++ b/lib/modules/traf_module/traf_request_model.dart @@ -51,7 +51,7 @@ class TrafRequestDataModel { String? employeeEmail; int? positionId; String? positionName; - String? departmentId; + dynamic departmentId; String? departmentName; List? departments; List? trafDepartments; @@ -498,6 +498,7 @@ class TrafRequestDataModel { } data['siteId'] = siteId; + data['departmentId'] = departmentId; data['siteName'] = siteName; data['requesterExtensionNumber'] = requesterExtensionNumber; data['requesterExtensionName'] = requesterExtensionName; diff --git a/lib/providers/lookups/customer_lookup_provider.dart b/lib/providers/lookups/customer_lookup_provider.dart new file mode 100644 index 00000000..c87b54c5 --- /dev/null +++ b/lib/providers/lookups/customer_lookup_provider.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/models/new_models/traf_customer.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +import '../../controllers/api_routes/api_manager.dart'; +import '../../controllers/api_routes/urls.dart'; + +class CustomerLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + // Get customers by user ID (GUID string) + // If id is null, try to get it from the current user + String? userId = id?.toString(); + + Response response = await ApiManager.instance.get( + URLs.getCustomersByUserId + "?userId=$userId", + useCache: true, + enableToastMessage: false, + ); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => TrafCustomer.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } + + // Add method to get data by string user ID (GUID) + Future getDataByUserId(String? userId) async { + if (loading == true) return -2; + if (userId == null || userId.isEmpty) return -1; + + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get( + URLs.getCustomersByUserId + "?userId=$userId", + useCache: true, + enableToastMessage: false, + ); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => TrafCustomer.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} From 0c5c8d05d06df607bf0178a9197936cf2d8bace4 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:26:32 +0300 Subject: [PATCH 15/73] improvements --- .../providers/api/all_requests_provider.dart | 25 +++++++++++-------- .../traf_module/create_traf_request_page.dart | 24 +++++++----------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index e3374962..8f68b966 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:flutter/widgets.dart'; import 'package:http/http.dart'; @@ -157,20 +158,20 @@ class AllRequestsProvider extends ChangeNotifier { } // switching asset inventory to users permissions - // context.userProvider.modulesPermissionsList.forEach((module) { - // if (module.module!.value! == 8 || module.module!.value! == 9) { - // list.add(module.module!.value!); - // } - // }); context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 8) { + if (module.module!.value! == 8 || module.module!.value! == 9) { list.add(module.module!.value!); } }); - - if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor) { - list.add(9); // - } + // context.userProvider.modulesPermissionsList.forEach((module) { + // if (module.module!.value! == 8) { + // list.add(module.module!.value!); + // } + // }); + // + // if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor) { + // list.add(9); // + // } list.add(12); if (context.userProvider.isCommissioningEngineer && context.settingProvider.isUserFlowMedical) { list = [13]; @@ -181,7 +182,9 @@ class AllRequestsProvider extends ChangeNotifier { // list = [16]; list.add(16); } - + list.forEach((element){ + log('request status are ${element.toString()}'); + }); return list; } diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 4535cdc9..02bae506 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -77,31 +78,24 @@ class _CreateTRAFRequestPageState extends State { void initState() { super.initState(); trafRequest = TrafRequestDataModel(id: 0, attachments: [], departments: [], qty: 1); - // Reset providers and load site data after the first frame to avoid provider lifecycle issues WidgetsBinding.instance.addPostFrameCallback((_) { - // Only reset providers if they don't have data (first time load) - // This prevents unnecessary loading when navigating back and forth final requestTypeProvider = Provider.of(context, listen: false); final yesNoProvider = Provider.of(context, listen: false); final customerProvider = Provider.of(context, listen: false); - - // Only load if not already loaded if (requestTypeProvider.items.isEmpty) { - requestTypeProvider.getData(); + requestTypeProvider.reset(); } - if (yesNoProvider.items.isEmpty) { - yesNoProvider.getData(); + yesNoProvider.reset(); } - - // Load customer/site data using the string user ID (GUID) only if not loaded - if (customerProvider.items.isEmpty) { - final userId = context.userProvider.user?.userID; - if (userId != null && userId.isNotEmpty) { - customerProvider.getDataByUserId(userId); - } + final userId = context.userProvider.user?.userID; + if (customerProvider.items.isEmpty && userId != null && userId.isNotEmpty) { + customerProvider.getDataByUserId(userId); } + log('user id is ${userId}'); + }); + } void resetProviders() { From df8948ea496051f814e88c69d4046f2e5c50c36b Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:37:41 +0300 Subject: [PATCH 16/73] null check added for priority value for CM --- .../views/components/service_request_detail_view.dart | 4 ++-- .../pages/land_page/requests/service_request_item_view.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 1310fb79..a6d4d880 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 @@ -135,10 +135,10 @@ class _ServiceRequestDetailViewState extends State { // spacing: 8, runSpacing: 8, children: [ - if (Provider.of(context, listen: false).showPriority) ...[ + if (Provider.of(context, listen: false).showPriority&&workOrder.priority!=null) ...[ StatusLabel( label: workOrder.priority?.name, - id: workOrder.priority!.value!, + id: workOrder.priority?.value, radius: 4, textColor: AppColor.getPriorityStatusTextColor(context, workOrder.priority!.value!), backgroundColor: AppColor.getPriorityStatusColor(context, workOrder.priority!.value!), diff --git a/lib/new_views/pages/land_page/requests/service_request_item_view.dart b/lib/new_views/pages/land_page/requests/service_request_item_view.dart index b24a01cc..f7128ad6 100644 --- a/lib/new_views/pages/land_page/requests/service_request_item_view.dart +++ b/lib/new_views/pages/land_page/requests/service_request_item_view.dart @@ -32,7 +32,7 @@ class ServiceRequestItemView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (Provider.of(context, listen: false).showPriority) ...[ + if (Provider.of(context, listen: false).showPriority && requestData?.priorityName != null && requestData!.priorityName!.isNotEmpty) ...[ StatusLabel( label: requestData!.priorityName!, textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.priorityName!), @@ -95,7 +95,7 @@ class ServiceRequestItemView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (Provider.of(context, listen: false).showPriority) ...[ + if (Provider.of(context, listen: false).showPriority && requestDetails!.priority != null && requestDetails!.priority!.isNotEmpty) ...[ StatusLabel( label: requestDetails!.priority, textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails!.priority), From d6f2d78351bb3b75d1f7ac3eb3378762f69df215 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:38:35 +0300 Subject: [PATCH 17/73] Revert "added identifier for loan request department model" This reverts commit f2b472f4df70ad7103da7f617196d2e2b12c2ca4. --- lib/models/new_models/traf_department.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/models/new_models/traf_department.dart b/lib/models/new_models/traf_department.dart index 3ac9841c..5a2e256d 100644 --- a/lib/models/new_models/traf_department.dart +++ b/lib/models/new_models/traf_department.dart @@ -3,9 +3,9 @@ import 'package:test_sa/models/new_models/room_model.dart'; import '../base.dart'; class TrafDepartment extends Base { - TrafDepartment( - {this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate}) - : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id + TrafDepartment({ + this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate + }) : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id TrafDepartment.fromJson(Map json) { departmentName = json['departmentName'] ?? json['name']; @@ -15,7 +15,6 @@ class TrafDepartment extends Base { costCenterName = json['costCenterName']; name = json['name']; id = json['id']; - identifier = id?.toString() ?? ''; createdBy = json['createdBy']; createdDate = json['createdDate']; modifiedBy = json['modifiedBy']; From 6cad1866e712b52d2eedadfbc5b0d81fe8a59d19 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:02:22 +0300 Subject: [PATCH 18/73] added identifier for loan creation department to match the department with cm request --- lib/models/new_models/traf_department.dart | 41 +++++++++---------- .../pages/create_loan_request_page.dart | 2 + 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/models/new_models/traf_department.dart b/lib/models/new_models/traf_department.dart index 5a2e256d..232470a4 100644 --- a/lib/models/new_models/traf_department.dart +++ b/lib/models/new_models/traf_department.dart @@ -3,9 +3,9 @@ import 'package:test_sa/models/new_models/room_model.dart'; import '../base.dart'; class TrafDepartment extends Base { - TrafDepartment({ - this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate - }) : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id + TrafDepartment( + {this.departmentName, this.departmentCode, this.ntCode, this.costCenterNumber, this.costCenterName, this.name, this.id, this.createdBy, this.createdDate, this.modifiedBy, this.modifiedDate}) + : super(identifier: id?.toString() ?? '', name: departmentName); // Handle potential null id TrafDepartment.fromJson(Map json) { departmentName = json['departmentName'] ?? json['name']; @@ -15,6 +15,7 @@ class TrafDepartment extends Base { costCenterName = json['costCenterName']; name = json['name']; id = json['id']; + identifier = id?.toString() ?? ''; createdBy = json['createdBy']; createdDate = json['createdDate']; modifiedBy = json['modifiedBy']; @@ -33,24 +34,22 @@ class TrafDepartment extends Base { String? modifiedBy; String? modifiedDate; - // TrafDepartment copyWith({ - // num? id, // Parameters are now nullable - // String? departmentName, - // String? departmentCode, - // String? departmentId, - // String? ntCode, - // List? rooms, - // }) => - // TrafDepartment( - // id: id ?? this.id, - // departmentName: departmentName ?? this.departmentName, - // departmentCode: departmentCode ?? this.departmentCode, - // departmentId: departmentId ?? this.departmentId, - // ntCode: ntCode ?? this.ntCode, - // rooms: rooms ?? this.rooms, - // ); - - +// TrafDepartment copyWith({ +// num? id, // Parameters are now nullable +// String? departmentName, +// String? departmentCode, +// String? departmentId, +// String? ntCode, +// List? rooms, +// }) => +// TrafDepartment( +// id: id ?? this.id, +// departmentName: departmentName ?? this.departmentName, +// departmentCode: departmentCode ?? this.departmentCode, +// departmentId: departmentId ?? this.departmentId, +// ntCode: ntCode ?? this.ntCode, +// rooms: rooms ?? this.rooms, +// ); } // class TrafDepartment extends Base { diff --git a/lib/modules/loan_module/pages/create_loan_request_page.dart b/lib/modules/loan_module/pages/create_loan_request_page.dart index 0b2dc53e..450ea806 100644 --- a/lib/modules/loan_module/pages/create_loan_request_page.dart +++ b/lib/modules/loan_module/pages/create_loan_request_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/validator/validator.dart'; import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/enum_extensions.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'; @@ -315,6 +316,7 @@ class _CreateLoanRequestPageState extends State with Tick SingleItemDropDownMenu( context: context, title: context.translation.department, + matchBy: DropDownMatchType.identifier, showShadow: false, enabled: !isBackUpRequest, validator: (value) { From 0e4714cd9aa36ec3d89e715d5f2fcc6135d0b43e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 14 Jun 2026 18:55:29 +0300 Subject: [PATCH 19/73] 1.7.6+41 uploaded on stores. --- lib/controllers/api_routes/urls.dart | 10 +++++----- .../land_page/create_request_type_bottom_sheet.dart | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index e2b02926..06107f57 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,15 +1,15 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "40"; + static const String appReleaseBuildNumber = "41"; - // static const host1 = "https://atomsm.hmg.com"; // production url + 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://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT - static final String _baseUrl = "$_host/v1/mobile"; // new V2 apis - // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/mobile"; // new V2 apis + static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart index 62a4e558..bb6cbfc6 100644 --- a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart +++ b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart @@ -118,8 +118,8 @@ class CreateRequestModel { if (context.userProvider.isQualityUser && context.settingProvider.isUserFlowMedical) { 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 && context.settingProvider.isUserFlowMedical) { - list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); + } else if (context.userProvider.isAssessor && context.settingProvider.isUserFlowMedical) { // todo: handled in permission api, so no need to check here + // list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); } else if (context.userProvider.isEngineer) { if (Provider.of(context, listen: false).engineerCanCreateCM) { list.add(CreateRequestModel(context.translation.correctiveMaintenance, "add_icon", CreateCMRequest.id)); diff --git a/pubspec.yaml b/pubspec.yaml index bf8e3371..be0dccbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.5+40 +version: 1.7.6+41 environment: sdk: ">=3.5.0 <4.0.0" From f66804b38ab685e668c6a9e8891d521711832c1c Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:59:54 +0300 Subject: [PATCH 20/73] changed the Traf name to Assessment of Need & Technology and fix department selection issue --- .../providers/api/all_requests_provider.dart | 29 +++++++-------- .../traf_module/create_traf_request_page.dart | 36 ++++++++----------- .../traf_module/traf_request_detail_page.dart | 4 +-- .../traf_module/traf_request_item_view.dart | 13 ++++--- .../create_request_type_bottom_sheet.dart | 12 +++++-- .../my_request/all_requests_filter_page.dart | 4 ++- .../my_request/my_requests_page.dart | 14 +++++--- 7 files changed, 62 insertions(+), 50 deletions(-) diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 8f68b966..49156233 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -157,21 +157,22 @@ class AllRequestsProvider extends ChangeNotifier { list.add(7); // task mod } - // switching asset inventory to users permissions - context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 8 || module.module!.value! == 9) { - list.add(module.module!.value!); - } - }); + //TODO uncommit this to enable TRAF in production. // context.userProvider.modulesPermissionsList.forEach((module) { - // if (module.module!.value! == 8) { + // if (module.module!.value! == 8 || module.module!.value! == 9) { // list.add(module.module!.value!); // } // }); - // - // if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor) { - // list.add(9); // - // } + //Todo commit this when passing to production. + context.userProvider.modulesPermissionsList.forEach((module) { + if (module.module!.value! == 8) { + list.add(module.module!.value!); + } + }); + + if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor && (context.userProvider.isNurse || context.userProvider.isEngineer)) { + list.add(9); // + } list.add(12); if (context.userProvider.isCommissioningEngineer && context.settingProvider.isUserFlowMedical) { list = [13]; @@ -182,9 +183,9 @@ class AllRequestsProvider extends ChangeNotifier { // list = [16]; list.add(16); } - list.forEach((element){ - log('request status are ${element.toString()}'); - }); + list.forEach((element) { + log('request status are ${element.toString()}'); + }); return list; } diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 02bae506..bc11f9dc 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -93,9 +93,7 @@ class _CreateTRAFRequestPageState extends State { customerProvider.getDataByUserId(userId); } log('user id is ${userId}'); - }); - } void resetProviders() { @@ -111,7 +109,7 @@ class _CreateTRAFRequestPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: const DefaultAppBar(title: "TRAF Request"), + appBar: const DefaultAppBar(title: "Assessment of Need & Technology"), body: Form( key: _formKey, child: Column( @@ -234,24 +232,20 @@ class _CreateTRAFRequestPageState extends State { }, ), 12.height, - Focus( - focusNode: siteDepartmentNode, - child: SingleItemDropDownMenu( - context: context, - showAsBottomSheet: true, - backgroundColor: AppColor.neutral100, - showShadow: false, - title: "Department", - requestById: null, - // Don't pass requestById to prevent auto-fetch - initialValue: selectedDepartment, - enabled: selectedSite != null, - onSelect: (value) { - selectedDepartment = value; - trafRequest?.departmentId = value?.id?.toString(); - setState(() {}); - }, - ), + SingleItemDropDownMenu( + context: context, + showAsBottomSheet: true, + backgroundColor: AppColor.neutral100, + showShadow: false, + title: "Department", + requestById: null, + // initialValue: selectedDepartment, + enabled: selectedSite != null, + onSelect: (value) { + selectedDepartment = value; + trafRequest?.departmentId = value?.id?.toString(); + setState(() {}); + }, ), 12.height, AppTextFormField( diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index e56b043a..548cb41b 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -57,7 +57,7 @@ class _TrafRequestDetailPageState extends State { Widget build(BuildContext context) { bool isEngineer = (Provider.of(context, listen: false).user?.type) == UsersTypes.engineer; return Scaffold( - appBar: const DefaultAppBar(title: "TRAF Request"), + appBar: const DefaultAppBar(title: "Assessment of Need & Technology"), body: Selector( selector: (_, myModel) => myModel.isLoading, // Selects only the userName builder: (_, isLoading, __) { @@ -129,7 +129,7 @@ class _TrafRequestDetailPageState extends State { style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), 8.height, - 'TRAF No: ${data.reqCode ?? '-'}'.bodyText(context), + 'Request No: ${data.reqCode ?? '-'}'.bodyText(context), 'Request type: ${data.requestTypeName ?? '-'}'.bodyText(context), if (data.requestTypeValue == 1) ...[ 4.height, diff --git a/lib/modules/traf_module/traf_request_item_view.dart b/lib/modules/traf_module/traf_request_item_view.dart index 1bf03eb5..68472b36 100644 --- a/lib/modules/traf_module/traf_request_item_view.dart +++ b/lib/modules/traf_module/traf_request_item_view.dart @@ -47,9 +47,11 @@ class TrafRequestItemView extends StatelessWidget { ], ), 8.height, - (requestData?.typeTransaction ?? "TRAF Request").heading5(context), + //TODO uncommit this when API will provide changed name for traf + // (requestData?.typeTransaction ?? "TRAF Request").heading5(context), + ("Assessment of Need & Technology").heading5(context), infoWidget(label: context.translation.requestType, value: requestData?.requestTypeName, context: context), - infoWidget(label: "TRAF No", value: requestData?.requestNo, context: context), + infoWidget(label: "Request No", value: requestData?.requestNo, context: context), 8.height, Row( mainAxisSize: MainAxisSize.min, @@ -93,10 +95,13 @@ class TrafRequestItemView extends StatelessWidget { ], ), 8.height, - (requestDetails?.nameOfType ?? "TRAF Request").heading5(context), + + //TODO uncommit this when API will provide changed name for traf + // (requestDetails?.nameOfType ?? "TRAF Request").heading5(context), + ("Assessment of Need & Technology").heading5(context), 8.height, infoWidget(label: context.translation.requestType, value: requestDetails!.requestType, context: context), - infoWidget(label: "TRAF No", value: requestDetails!.requestNo, context: context), + infoWidget(label: "Request No", value: requestDetails!.requestNo, context: context), infoWidget(label: context.translation.site, value: requestDetails!.site, context: context), 8.height, Row( diff --git a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart index 62a4e558..d673c3dc 100644 --- a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart +++ b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart @@ -147,11 +147,17 @@ class CreateRequestModel { // list.add(CreateRequestModel('Demo Request'.addTranslation, "add_icon", CreateDemoRequestPage.id)); } } + if(context.settingProvider.isUserFlowMedical&&(context.userProvider.isAssessor||context.userProvider.isEngineer||context.userProvider.isNurse)){ + //Traf name changed to Assessment of Need & Technology by client . + list.add(CreateRequestModel("Assessment of Need & Technology".addTranslation, "add_icon", CreateTRAFRequestPage.id)); + + } context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 9 && (module.canAdd ?? false)) { - list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); - } + ///TODO uncommit this to enable TRAF in production. + // if (module.module!.value! == 9 && (module.canAdd ?? false)) { + // list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); + // } if (module.module!.value! == 15 && (module.canAdd ?? false)) { list.add(CreateRequestModel('Demo Request'.addTranslation, "add_icon", CreateDemoRequestPage.id)); } 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 9b5a7441..b05a8497 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 @@ -120,7 +120,9 @@ class _AllRequestsFilterPageState extends State { ///TODO need add accessor filter as well. if (context.settingProvider.isUserFlowMedical && context.userProvider.isAssessor) { types = { - 'TRAF': 9, + // 'TRAF': 9, + //Traf name changed as per client requirements + 'Assessment of Need & Technology': 9, // 'Demo': 15, moved to permission api }; } 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 ba49458e..c9d3d946 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 @@ -58,14 +58,16 @@ class _MyRequestsPageState extends State { // switching asset inventory to users permissions context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) { + ///todo uncommit this in prod currently apis is not giving the correct name for traf + // if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) { + if (module.module!.value! == 8 || module.module!.value! == 15) { requestsList.add(Request(module.module!.value!, module.module!.name!.addTranslation)); } }); //Hide TRAF. - // if (context.settingProvider.isUserFlowMedical && context.userProvider.user!.type == UsersTypes.normal_user) { - // requestsList.add(Request(9, 'TRAF')); - // } + if (context.settingProvider.isUserFlowMedical && context.userProvider.user!.type == UsersTypes.normal_user) { + requestsList.add(Request(9, 'Assessment of Need & Technology')); + } if (context.userProvider.isEngineer && context.settingProvider.isUserFlowMedical) { requestsList.add(Request(10, 'Equipment Internal Audit')); requestsList.add(Request(11, 'System Internal Audit')); @@ -82,7 +84,9 @@ class _MyRequestsPageState extends State { if (context.userProvider.isAssessor) { requestsList = [ Request(null, context.translation.allWorkOrder), - Request(9, 'TRAF'), + // Request(9, 'TRAF'), + //name changed as requested by client + Request(9, 'Assessment of Need & Technology'), // Request(15, 'Demo'), moved to permission api ]; } From 891a67951833caf00812f1ff60043523edab7492 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 16 Jun 2026 13:18:43 +0300 Subject: [PATCH 21/73] traf code switch to permission api. --- .../providers/api/all_requests_provider.dart | 22 +++++------ .../create_request_type_bottom_sheet.dart | 15 ++++---- .../my_request/my_requests_page.dart | 9 ++--- .../lookups/customer_lookup_provider.dart | 37 +------------------ 4 files changed, 24 insertions(+), 59 deletions(-) diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 49156233..99c58970 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -158,21 +158,21 @@ class AllRequestsProvider extends ChangeNotifier { } //TODO uncommit this to enable TRAF in production. - // context.userProvider.modulesPermissionsList.forEach((module) { - // if (module.module!.value! == 8 || module.module!.value! == 9) { - // list.add(module.module!.value!); - // } - // }); - //Todo commit this when passing to production. context.userProvider.modulesPermissionsList.forEach((module) { - if (module.module!.value! == 8) { + if (module.module!.value! == 8 || module.module!.value! == 9) { list.add(module.module!.value!); } }); - - if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor && (context.userProvider.isNurse || context.userProvider.isEngineer)) { - list.add(9); // - } + //Todo commit this when passing to production. + // context.userProvider.modulesPermissionsList.forEach((module) { + // if (module.module!.value! == 8) { + // list.add(module.module!.value!); + // } + // }); + // + // if (context.settingProvider.isUserFlowMedical && !context.userProvider.isAssessor && (context.userProvider.isNurse || context.userProvider.isEngineer)) { + // list.add(9); // + // } list.add(12); if (context.userProvider.isCommissioningEngineer && context.settingProvider.isUserFlowMedical) { list = [13]; diff --git a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart index d673c3dc..0d189bb7 100644 --- a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart +++ b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart @@ -147,17 +147,16 @@ class CreateRequestModel { // list.add(CreateRequestModel('Demo Request'.addTranslation, "add_icon", CreateDemoRequestPage.id)); } } - if(context.settingProvider.isUserFlowMedical&&(context.userProvider.isAssessor||context.userProvider.isEngineer||context.userProvider.isNurse)){ - //Traf name changed to Assessment of Need & Technology by client . - list.add(CreateRequestModel("Assessment of Need & Technology".addTranslation, "add_icon", CreateTRAFRequestPage.id)); - - } + // if (context.settingProvider.isUserFlowMedical && (context.userProvider.isAssessor || context.userProvider.isEngineer || context.userProvider.isNurse)) { + // //Traf name changed to Assessment of Need & Technology by client . + // list.add(CreateRequestModel("Assessment of Need & Technology".addTranslation, "add_icon", CreateTRAFRequestPage.id)); + // } context.userProvider.modulesPermissionsList.forEach((module) { ///TODO uncommit this to enable TRAF in production. - // if (module.module!.value! == 9 && (module.canAdd ?? false)) { - // list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); - // } + if (module.module!.value! == 9 && (module.canAdd ?? false)) { + list.add(CreateRequestModel(module.module?.name ?? "Assessment of Need & Technology".addTranslation, "add_icon", CreateTRAFRequestPage.id)); + } if (module.module!.value! == 15 && (module.canAdd ?? false)) { list.add(CreateRequestModel('Demo Request'.addTranslation, "add_icon", CreateDemoRequestPage.id)); } 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 c9d3d946..8294a838 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 @@ -59,15 +59,14 @@ class _MyRequestsPageState extends State { // switching asset inventory to users permissions context.userProvider.modulesPermissionsList.forEach((module) { ///todo uncommit this in prod currently apis is not giving the correct name for traf - // if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) { - if (module.module!.value! == 8 || module.module!.value! == 15) { + if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) { requestsList.add(Request(module.module!.value!, module.module!.name!.addTranslation)); } }); //Hide TRAF. - if (context.settingProvider.isUserFlowMedical && context.userProvider.user!.type == UsersTypes.normal_user) { - requestsList.add(Request(9, 'Assessment of Need & Technology')); - } + // if (context.settingProvider.isUserFlowMedical && context.userProvider.user!.type == UsersTypes.normal_user) { + // requestsList.add(Request(9, 'Assessment of Need & Technology')); + // } if (context.userProvider.isEngineer && context.settingProvider.isUserFlowMedical) { requestsList.add(Request(10, 'Equipment Internal Audit')); requestsList.add(Request(11, 'System Internal Audit')); diff --git a/lib/providers/lookups/customer_lookup_provider.dart b/lib/providers/lookups/customer_lookup_provider.dart index c87b54c5..086154a1 100644 --- a/lib/providers/lookups/customer_lookup_provider.dart +++ b/lib/providers/lookups/customer_lookup_provider.dart @@ -9,37 +9,8 @@ import '../../controllers/api_routes/urls.dart'; class CustomerLookupProvider extends LoadingListNotifier { @override - Future getData({int? id}) async { - if (loading == true) return -2; - loading = true; - notifyListeners(); - try { - // Get customers by user ID (GUID string) - // If id is null, try to get it from the current user - String? userId = id?.toString(); - - Response response = await ApiManager.instance.get( - URLs.getCustomersByUserId + "?userId=$userId", - useCache: true, - enableToastMessage: false, - ); - stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - List categoriesListJson = json.decode(response.body)["data"]; - items = categoriesListJson.map((item) => TrafCustomer.fromJson(item)).toList(); - } - loading = false; - notifyListeners(); - return response.statusCode; - } catch (error) { - loading = false; - stateCode = -1; - notifyListeners(); - return -1; - } - } + Future getData({int? id}) async {} - // Add method to get data by string user ID (GUID) Future getDataByUserId(String? userId) async { if (loading == true) return -2; if (userId == null || userId.isEmpty) return -1; @@ -47,11 +18,7 @@ class CustomerLookupProvider extends LoadingListNotifier { loading = true; notifyListeners(); try { - Response response = await ApiManager.instance.get( - URLs.getCustomersByUserId + "?userId=$userId", - useCache: true, - enableToastMessage: false, - ); + Response response = await ApiManager.instance.get(URLs.getCustomersByUserId + "?userId=$userId", useCache: true, enableToastMessage: false); stateCode = response.statusCode; if (response.statusCode >= 200 && response.statusCode < 300) { List categoriesListJson = json.decode(response.body)["data"]; From eec6f5e31b955657d32a511b9fad7e3877c9de66 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 18 Jun 2026 17:37:31 +0300 Subject: [PATCH 22/73] v1.7.7+42 uploaded on stores. --- lib/controllers/api_routes/urls.dart | 10 +++++----- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 3669fdc5..8995f023 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,15 +1,15 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "41"; + static const String appReleaseBuildNumber = "42"; - // static const host1 = "https://atomsm.hmg.com"; // production url + 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://atomsmuat.hmg.com"; // local UAT url - static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/pubspec.yaml b/pubspec.yaml index be0dccbc..afce5ab7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.6+41 +version: 1.7.7+42 environment: sdk: ">=3.5.0 <4.0.0" From 3766c6936857cea2fc5b4578fb00dffb96e6bcf7 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 21 Jun 2026 12:43:38 +0300 Subject: [PATCH 23/73] merge conflict resolved --- lib/dashboard_latest/dashboard_provider.dart | 7 +- .../widgets/progress_fragment.dart | 9 +- .../widgets/request_category_fragment.dart | 90 +++++++++++-------- .../pages/cleaner_request_item_view.dart | 0 .../pages/one_time_task_view.dart | 21 ++--- .../traf_module/traf_request_detail_page.dart | 1 + .../traf_module/traf_request_item_view.dart | 42 ++------- .../requests/recurrent_wo_item_view.dart | 62 ++++--------- 8 files changed, 92 insertions(+), 140 deletions(-) delete mode 100644 lib/modules/cleaner_module/pages/cleaner_request_item_view.dart diff --git a/lib/dashboard_latest/dashboard_provider.dart b/lib/dashboard_latest/dashboard_provider.dart index 4f8df139..77896468 100644 --- a/lib/dashboard_latest/dashboard_provider.dart +++ b/lib/dashboard_latest/dashboard_provider.dart @@ -580,8 +580,11 @@ class CategoryTabs { // tabs.add(CategoryTabs(label: 'Upcoming', tag: 0, id: 0)); // tabs.add(CategoryTabs('Open', 1)); // tabs.add(CategoryTabs(label: 'UnAssigned', tag: 1, id: 1)); - tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 2)); - tabs.add(CategoryTabs(label: 'Completed', tag: 4, id: 3)); + // tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 2)); + // tabs.add(CategoryTabs(label: 'Completed', tag: 4, id: 3)); + + tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 2, requestType: RequestTypeEnum.inProgress, icon: "dashboard/inprogress_icon", iconColor: AppColor.primary10)); + tabs.add(CategoryTabs(label: 'Completed', tag: 4, id: 3, requestType: RequestTypeEnum.completed, icon: "complete_request", iconColor: AppColor.completedColor)); return tabs; } diff --git a/lib/dashboard_latest/widgets/progress_fragment.dart b/lib/dashboard_latest/widgets/progress_fragment.dart index f3cd90b8..70d2dfcf 100644 --- a/lib/dashboard_latest/widgets/progress_fragment.dart +++ b/lib/dashboard_latest/widgets/progress_fragment.dart @@ -235,9 +235,8 @@ class _ProgressFragmentState extends State with SingleTickerPr } else if (_userProvider.isHouseKeeper) { total = (snapshot.dashboardCount?.data?.countClose ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); chartData = [ - ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.orange70), - ChartData('Completed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.green70), - // ChartData('Open', snapshot.dashboardCount?.data?.countOpen?.toDouble() ?? 0.0, AppColor.blueStatus(context)), + ChartData('In Progress', snapshot.dashboardCount?.data?.countInprogress?.toDouble() ?? 0.0, AppColor.inProgressChartColor, "dashboard/inprogress_icon"), + ChartData('Completed', snapshot.dashboardCount?.data?.countClose?.toDouble() ?? 0.0, AppColor.completeChartColor, "dashboard/completed_icon"), ]; } else { total = (snapshot.dashboardCount?.data?.countComplete ?? 0) + (snapshot.dashboardCount?.data?.countInprogress ?? 0) + (snapshot.dashboardCount?.data?.countOpen ?? 0); @@ -319,7 +318,7 @@ class _ProgressFragmentState extends State with SingleTickerPr ), ).toShimmer(isShow: snapshot.isAllCountLoading, radius: 250, context: context), ), - ).paddingOnly(top: 16,bottom: 16), + ).paddingOnly(top: 16, bottom: 16), ), Expanded( flex: 2, @@ -347,7 +346,7 @@ class _ProgressFragmentState extends State with SingleTickerPr ], ) ], - ).paddingOnly(top: 4,bottom: 4), + ).paddingOnly(top: 4, bottom: 4), ), ], ).expanded, diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index e20da541..52cbf259 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -116,47 +116,51 @@ class _RequestCategoryFragmentState extends State with final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - !isNurse && dashboardProvider.tabs.isNotEmpty && dashboardProvider.tabs[dashboardProvider.currentListIndex].tag == 0 - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - context.translation.allWorkOrder.heading4(context), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // context.translation.allWorkOrder.heading4(context), - // GestureDetector( - // onTap: () { - // dashboardProvider.isShowDashBoardCalendar = !dashboardProvider.isShowDashBoardCalendar; - // }, - // child: dashboardProvider.isShowDashBoardCalendar ? AppAsset.listIcon.toSvgAsset() : AppAsset.calendarIcon.toSvgAsset()) - // ], - // ), - dashboardProvider.isShowDashBoardCalendar - ? Padding( - padding: EdgeInsets.only(top: 12.toScreenHeight), - child: const WeeklyCalendarFragment(), - ) - : const SizedBox(), - ], - ).paddingOnly(start: 14, end: 12) - : isHouseKeeper - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 'All Tasks'.addTranslation.heading4(context), - 'add_icon'.toSvgAsset(width: 20, height: 20).onPress(() { - Navigator.of(context).pushNamed('/cleaner_search'); - }), - ], - ).paddingOnly(start: 16, end: 16) - : context.translation.allWorkOrder.heading4(context).paddingOnly(start: 16, end: 16), + + + // !isNurse && dashboardProvider.tabs.isNotEmpty && dashboardProvider.tabs[dashboardProvider.currentListIndex].tag == 0 + // ? Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // context.translation.allWorkOrder.heading4(context), + // // Row( + // // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // // children: [ + // // context.translation.allWorkOrder.heading4(context), + // // GestureDetector( + // // onTap: () { + // // dashboardProvider.isShowDashBoardCalendar = !dashboardProvider.isShowDashBoardCalendar; + // // }, + // // child: dashboardProvider.isShowDashBoardCalendar ? AppAsset.listIcon.toSvgAsset() : AppAsset.calendarIcon.toSvgAsset()) + // // ], + // // ), + // dashboardProvider.isShowDashBoardCalendar + // ? Padding( + // padding: EdgeInsets.only(top: 12.toScreenHeight), + // child: const WeeklyCalendarFragment(), + // ) + // : const SizedBox(), + // ], + // ).paddingOnly(start: 14, end: 12) + // : isHouseKeeper + // ? Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // 'All Tasks'.addTranslation.heading4(context), + // 'add_icon'.toSvgAsset(width: 20, height: 20).onPress(() { + // Navigator.of(context).pushNamed('/cleaner_search'); + // }), + // ], + // ).paddingOnly(start: 16, end: 16) + // : context.translation.allWorkOrder.heading4(context).paddingOnly(start: 16, end: 16), + // + getTabs( requestsProvider: dashboardProvider, context: context, userType: Provider.of(context, listen: false).user?.type, ), - FadeTransition(f + FadeTransition( opacity: _headerFadeAnimation, child: SlideTransition( position: _headerSlideAnimation, @@ -187,7 +191,19 @@ class _RequestCategoryFragmentState extends State with // ], // ).paddingOnly(start: 14, end: 12) // : - Row( + + + isHouseKeeper + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'All Tasks'.addTranslation.heading4(context), + 'add_icon'.toSvgAsset(width: 20, height: 20).onPress(() { + Navigator.of(context).pushNamed('/cleaner_search'); + }), + ], + ).paddingOnly(start: 16, end: 16) : + Row( spacing: 16, children: [ Text(context.translation.allWorkOrder, style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context))).expanded, diff --git a/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart b/lib/modules/cleaner_module/pages/cleaner_request_item_view.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index d40921cb..85636944 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -77,12 +77,8 @@ class _OneTimeTaskViewState extends State { _taskData?.imageRooms ??= []; // Convert attachments from API to imageRooms format for display - if ((_taskData?.imageRooms?.isEmpty ?? true) && - (_taskData?.attachments != null && _taskData!.attachments!.isNotEmpty)) { - _taskData!.imageRooms = _taskData!.attachments! - .map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '') - .where((url) => url.isNotEmpty) - .toList(); + if ((_taskData?.imageRooms?.isEmpty ?? true) && (_taskData?.attachments != null && _taskData!.attachments!.isNotEmpty)) { + _taskData!.imageRooms = _taskData!.attachments!.map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '').where((url) => url.isNotEmpty).toList(); } _isLoading = false; @@ -135,13 +131,13 @@ class _OneTimeTaskViewState extends State { appBar: DefaultAppBar( title: 'One Time Task'.addTranslation, onWillPopScope: null, - onBackPress: (){ + onBackPress: () { WidgetsBinding.instance.addPostFrameCallback((_) { UserProvider userProvider = Provider.of(context, listen: false); DashBoardProvider dashBoardProvider = Provider.of(context, listen: false); - dashBoardProvider.getDashBoardCount(usersType:userProvider.user!.type!); + dashBoardProvider.getDashBoardCount(usersType: userProvider.user!.type!); dashBoardProvider.resetRequestDataList(); - dashBoardProvider.getRequestDetail(usersType: userProvider.user!.type!, status: dashBoardProvider.tabs[dashBoardProvider.currentListIndex].tag); + dashBoardProvider.getRequestDetail(usersType: userProvider.user!.type!, status: dashBoardProvider.tabs[dashBoardProvider.currentListIndex!].tag); }); Navigator.pop(context); }, @@ -219,8 +215,8 @@ class _OneTimeTaskViewState extends State { StatusLabel( radius: 4, label: _taskData!.statusName!, - textColor: AppColor.getHistoryLogStatusTextColorByName(_taskData!.statusName!), - backgroundColor: AppColor.getHistoryLogStatusColorByName(_taskData!.statusName!), + textColor: AppColor.getHistoryLogStatusTextColorByName(context, _taskData!.statusName!), + backgroundColor: AppColor.getHistoryLogStatusColorByName(context, _taskData!.statusName!), ), if (_taskData?.createdDate != null) Text( @@ -265,7 +261,8 @@ class _OneTimeTaskViewState extends State { endAt: null, durationInSecond: 0, ), - enabled: false, // Disable controls, just show the timer + enabled: false, + // Disable controls, just show the timer showTimer: true, decoration: BoxDecoration( color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 73543037..d6d428d6 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -11,6 +11,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/enums/user_types.dart'; import 'package:test_sa/modules/traf_module/traf_request_provider.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/images/files_list.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; diff --git a/lib/modules/traf_module/traf_request_item_view.dart b/lib/modules/traf_module/traf_request_item_view.dart index 4f4cce3e..3e74c425 100644 --- a/lib/modules/traf_module/traf_request_item_view.dart +++ b/lib/modules/traf_module/traf_request_item_view.dart @@ -44,24 +44,9 @@ class TrafRequestItemView extends StatelessWidget { ], ), 8.height, - (requestData?.typeTransaction ?? "Assessment of Need & Technology").heading5(context), // todo replace to info header - infoWidget(label: context.translation.requestType, value: requestData?.requestTypeName, context: context), - infoWidget(label: "Request No", value: requestData?.requestNo, 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) - ], - ), - InfoHeaderWidget(requestData?.typeTransaction ?? "TRAF Request"), + InfoHeaderWidget(requestData?.typeTransaction ?? "Assessment of Need & Technology"), InfoTextWidget(label: context.translation.requestType, value: requestData?.requestTypeName), - InfoTextWidget(label: "TRAF No", value: requestData?.requestNo, showArrow: true), + InfoTextWidget(label: "Request No", value: requestData?.requestNo, showArrow: true), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { Navigator.push(context, MaterialPageRoute(builder: (context) => TrafRequestDetailPage(trafId: requestData!.id!))); @@ -90,27 +75,10 @@ class TrafRequestItemView extends StatelessWidget { ], ), 8.height, - (requestDetails?.nameOfType ?? "Assessment of Need & Technology").heading5(context), - 8.height, - infoWidget(label: context.translation.requestType, value: requestDetails!.requestType, context: context), - infoWidget(label: "Request No", value: requestDetails!.requestNo, context: context), - 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) - ], - ), - InfoHeaderWidget(requestDetails?.nameOfType ?? "TRAF Request"), + InfoHeaderWidget(requestDetails?.nameOfType ?? "Assessment of Need & Technology"), InfoTextWidget(label: context.translation.requestType, value: requestDetails!.requestType), - InfoTextWidget(label: "TRAF No", value: requestDetails!.requestNo), - InfoTextWidget(label: context.translation.site, value: requestDetails!.site, showEmptyValue: true,showArrow: true), + InfoTextWidget(label: "Request No", value: requestDetails!.requestNo), + InfoTextWidget(label: context.translation.site, value: requestDetails!.site, showEmptyValue: true, showArrow: true), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { Navigator.push(context, MaterialPageRoute(builder: (context) => TrafRequestDetailPage(trafId: requestDetails!.id!))); diff --git a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart index 9ee7b760..d7ee1e9f 100644 --- a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart +++ b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart @@ -49,31 +49,15 @@ class RecurrentWoItemView extends StatelessWidget { ], ), 8.height, - (requestData?.typeTransaction ?? context.translation.recurrentWo).heading5(context), - 8.height, - // '${context.translation.assetNumber}: ${requestData!.assetNumber}'.bodyText(context), - // '${context.translation.assetSN}: ${request.assetSN}'.bodyText(context),//todo ask ahmed - // '${context.translation.code}: ${request.code}'.bodyText(context), - '${context.translation.taskNo}: ${requestData!.requestNo}'.bodyText(context), - if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), - if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), - if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), - if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), - if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(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) - ], - ), - InfoHeaderWidget(requestData?.typeTransaction ?? context.translation.ppmRequest), + + InfoHeaderWidget(requestData?.typeTransaction ?? context.translation.recurrentWo), InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo, showArrow: true), + // todo @waseem replace below code to infotextwidget + // if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), + // if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), + // if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), + // if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), + // if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(context), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() { Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestData!.id))); @@ -103,32 +87,16 @@ class RecurrentWoItemView extends StatelessWidget { ], ), 8.height, - (requestDetails?.nameOfType ?? context.translation.recurrentWo).heading5(context), - 8.height, - // '${context.translation.taskNo}: ${requestDetails!.id}'.bodyText(context), - '${context.translation.site}: ${requestDetails!.site}'.bodyText(context), - // '${context.translation.code}: ${request.code}'.bodyText(context), - '${context.translation.taskNo}: ${requestDetails!.requestNo}'.bodyText(context), - if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), - if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), - if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), - if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), - if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(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) - ], - ), InfoHeaderWidget(requestDetails?.nameOfType ?? context.translation.recurrentWo), InfoTextWidget(label: context.translation.site, value: requestDetails!.site), InfoTextWidget(label: context.translation.taskNo, value: requestDetails!.requestNo, showArrow: true), + + // todo @waseem replace below code to infotextwidget + // if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), + // if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), + // if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), + // if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), + // if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(context), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestDetails!.id))); From 65335dca8722cdc85db4df4b1a1110b89e3bba9e Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:56:31 +0300 Subject: [PATCH 24/73] implement uiux changes on cleaner module --- lib/controllers/api_routes/urls.dart | 4 +- .../widgets/request_category_fragment.dart | 2 +- .../pages/cleaner_search_details_view.dart | 413 ------------------ .../pages/cleaner_search_form_view.dart | 84 ++-- .../pages/one_time_task_preview_screen.dart | 110 ++--- .../pages/one_time_task_view.dart | 69 +-- .../widgets/cleaner_checklist_widget.dart | 101 ++--- .../widgets/one_time_task_item_view.dart | 75 ++-- .../requests/recurrent_wo_item_view.dart | 39 +- 9 files changed, 176 insertions(+), 721 deletions(-) delete mode 100644 lib/modules/cleaner_module/pages/cleaner_search_details_view.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 4abba7ea..36531408 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -7,8 +7,8 @@ class URLs { // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT - static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index 52cbf259..488ff6a0 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -252,7 +252,7 @@ class _RequestCategoryFragmentState extends State with snapshot.allRequestsAndCount?.requestsDetails ?? [], snapshot.isAllLoading, disableScroll: true, - key: ValueKey('request_list_'), // Key changes on every tab click + key: const ValueKey('request_list_'), // Key changes on every tab click ); }) : dashboardProvider.listIndex.length == 1 && dashboardProvider.isUnassignedTab(dashboardProvider.listIndex[0]) diff --git a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart deleted file mode 100644 index 6bbf5ef1..00000000 --- a/lib/modules/cleaner_module/pages/cleaner_search_details_view.dart +++ /dev/null @@ -1,413 +0,0 @@ -// UI: Cleaner Search Details View -import 'dart:convert'; -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/ppm/recurrent_wo.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/new_views/common_widgets/app_lazy_loading.dart'; -import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; -import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; -import 'package:test_sa/views/widgets/qr/scan_qr.dart'; -import 'package:test_sa/views/widgets/requests/request_status.dart'; -import '../models/cleaner_task_search_result.dart'; -import '../provider/cleaner_provider.dart'; -import '../../pm_module/recurrent_wo/recurrent_work_order_view.dart'; -import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; -import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; -import 'package:test_sa/controllers/api_routes/api_manager.dart'; -import 'package:test_sa/controllers/api_routes/urls.dart'; -import 'package:http/http.dart' as http; - -class CleanerSearchDetailsView extends StatefulWidget { - static const String routeName = '/cleaner_search_details'; - final int requestId; - final CleanerTaskSearchResult? taskResult; - - const CleanerSearchDetailsView({ - Key? key, - required this.requestId, - this.taskResult, - }) : super(key: key); - - @override - _CleanerSearchDetailsViewState createState() => _CleanerSearchDetailsViewState(); -} - -class _CleanerSearchDetailsViewState extends State { - late CleanerProvider _cleanerProvider; - - // bool _isQRScanned = false; - - bool _isRequestOpen = false; - CleanerTaskSearchResult? _taskData; - RecurrentWoData? _recurrentWoData; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - _cleanerProvider = Provider.of(context, listen: false); - _taskData = widget.taskResult; - WidgetsBinding.instance.addPostFrameCallback((_) { - getInitialData(); - }); - } - - Future getInitialData() async { - // If taskResult is null, fetch data from recurrent work order API - if (widget.taskResult == null) { - setState(() { - _isLoading = true; - }); - - try { - // Use direct API call to get better error messages - final result = await _getRecurrentWoByIdWithMessage(widget.requestId); - - if (result["success"] == true && result["data"] != null) { - _recurrentWoData = result["data"]; - _isRequestOpen = _recurrentWoData?.status?.value == 4; - _taskData = CleanerTaskSearchResult( - id: _recurrentWoData!.id, - taskNo: _recurrentWoData!.taskNo, - siteName: _recurrentWoData!.site?.siteName, - buildingName: _recurrentWoData!.building?.name, - floorName: _recurrentWoData!.floor?.name, - departmentName: _recurrentWoData!.department?.name, - roomName: _recurrentWoData!.room?.name, - locationDescription: null, - createdDate: _recurrentWoData!.scheduleDate, - ); - } else { - // Show exact backend error message - if (mounted) { - _showErrorDialog(context, result["message"] ?? "Failed to load request details. Please try again."); - } - } - } catch (e) { - debugPrint('Error fetching recurrent work order: $e'); - if (mounted) { - _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); - } - } finally { - setState(() { - _isLoading = false; - }); - } - } else { - try { - await _cleanerProvider.getCleanerRequestById(id: widget.requestId); - } catch (e) { - debugPrint('Error fetching cleaner request: $e'); - if (mounted) { - _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); - } - } - } - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - appBar: DefaultAppBar( - title: 'Request Details'.addTranslation, - onBackPress: () => Navigator.pop(context), - ), - body: _isLoading - ? const Center(child: CircularProgressIndicator()) - : SafeArea( - child: Selector( - selector: (_, model) => model.isLoading, - builder: (_, isLoading, __) { - if (isLoading) { - return const Center(child: CircularProgressIndicator()); - } - - return Column( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildCleanerRequestDetailCard(context), - ], - ), - ).expanded, - FooterActionButton.footerContainer( - context: context, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (_isRequestOpen) ...[ - Row( - children: [ - Icon(Icons.info_outline_rounded, size: 16, color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - 8.width, - Expanded( - child: Text( - 'By scanning the QR code you will accept this task'.addTranslation, - // textAlign: TextAlign.center, - style: AppTextStyles.bodyText2.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - ), - ], - ), - 8.height, - ], - AppFilledButton( - label: _isRequestOpen ? 'Scan QR Code' : 'Update', - maxWidth: true, - buttonColor: AppColor.primary10, - onPressed: () async { - if (_isRequestOpen) { - final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanQr())); - if (result != null && result is String && result.isNotEmpty) { - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - - try { - final qrResult = await CleanerProvider().checkQRSameRoomWithMessage(planRecurrentTaskId: widget.requestId, qrCode: result); - if (mounted) Navigator.pop(context); - - final isValid = qrResult["success"] ?? false; - final backendMessage = qrResult["message"] ?? "Unknown error occurred"; - - if (isValid) { - if (mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), - ); - } - } else { - if (mounted) { - // Show exact backend error message instead of our custom message - _showErrorDialog(context, backendMessage); - } - } - } catch (e) { - if (mounted) Navigator.pop(context); - if (mounted) { - // Extract actual error message from the exception or use generic fallback - String errorMessage = _extractErrorMessage(e) ?? 'Failed to validate QR code. Please try again.'; - _showErrorDialog(context, errorMessage); - } - } - } - } else { - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), - ); - } - }, - ), - ], - ), - ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), - ], - ); - }, - ), - ), - ); - } - - Widget _buildCleanerRequestDetailCard(BuildContext context) { - final taskResult = _taskData; - - if (taskResult == null) { - return const SizedBox(); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Task Number and Date Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Text( - // 'Request Information', - // style: AppTextStyles.heading4.copyWith( - // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - // ), - // ), - if (_recurrentWoData?.status?.name != null) - StatusLabel( - label: _recurrentWoData?.status?.name!, - textColor: AppColor.getRequestStatusTextColorByName(context, _recurrentWoData?.status?.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, _recurrentWoData?.status?.name!), - ), - 8.width, - // StatusLabel( - // label: taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!, - // textColor: AppColor.getRequestStatusTextColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), - // backgroundColor: AppColor.getRequestStatusColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), - // ), - if (taskResult.createdDate != null) - Text( - taskResult.createdDate!.toServiceRequestCardFormat, - textAlign: TextAlign.end, - style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), - ], - ), - 8.height, - if (_recurrentWoData?.title?.isNotEmpty ?? false) - Text( - _recurrentWoData?.title ?? "", - style: AppTextStyles.heading4.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), - ), - if (taskResult.taskNo != null && taskResult.taskNo!.isNotEmpty) - Text( - 'Task No: ${taskResult.taskNo!}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.siteName != null && taskResult.siteName!.isNotEmpty) - Text( - 'Site: ${taskResult.siteName}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.buildingName != null && taskResult.buildingName!.isNotEmpty) - Text( - 'Building: ${taskResult.buildingName}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.floorName != null && taskResult.floorName!.isNotEmpty) - Text( - 'Floor: ${taskResult.floorName}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.departmentName != null && taskResult.departmentName!.isNotEmpty) - Text( - 'Department: ${taskResult.departmentName}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.roomName != null && taskResult.roomName!.isNotEmpty) - Text( - 'Room: ${taskResult.roomName}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (taskResult.locationDescription != null && taskResult.locationDescription!.isNotEmpty) - Text( - 'Location: ${taskResult.locationDescription}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ], - ).expanded, - ], - ), - ], - ).paddingOnly(start: 16, end: 16, top: 16, bottom: 8), - ], - ).toShadowContainer(context, padding: 0); - } - - void _showErrorDialog(BuildContext context, String message) { - showDialog( - context: context, - builder: (context) => ConfirmDialog( - message: message, - title: 'Error', - onTap: () => Navigator.pop(context), - ), - ); - } - - String? _extractErrorMessage(dynamic error) { - // Implement your logic to extract error message from the exception - // For example, if the error is a custom exception with a message property: - if (error is ExceptionWithMessage) { - return error.message; - } else if (error is String) { - // If the error is a string, return it directly - return error; - } else if (error is Map) { - // If the error is a Map (e.g., parsed JSON), extract the message field - return error['message'] ?? error.toString(); - } - - // Fallback to a generic error message - return 'An unexpected error occurred. Please try again.'; - } - - /// Custom method to get recurrent work order with proper error message extraction - Future> _getRecurrentWoByIdWithMessage(int id) async { - try { - final response = await ApiManager.instance.get( - URLs.getRecurrentPlanByIdUrl + "?planRecurrentTaskId=$id", - enableToastMessage: false, // Disable automatic toast - ); - - final responseData = json.decode(response.body); - - if (response.statusCode >= 200 && response.statusCode < 300) { - // Success response - final recurrentWoData = RecurrentWoData.fromJson(responseData["data"]); - - // Apply the same processing as in AllRequestsProvider - recurrentWoData.planRecurrentMedicalTaskRooms?.forEach((element) { - element.planRecurrentMedicalTaskRoomTabs?.forEach((tabs) { - tabs.planRecurrentMedicalTaskRoomTabAttributes?.forEach((attribute) { - if (attribute.attribute?.type == "bool" && attribute.attributeValue == null) { - attribute.attributeValue = "true"; - } - }); - }); - }); - - return { - "success": true, - "data": recurrentWoData, - "message": "Data loaded successfully", - }; - } else { - // Error response - extract the exact error message from backend - return { - "success": false, - "data": null, - "message": responseData["message"] ?? "Failed to load request details", - }; - } - } catch (error) { - debugPrint('Error in _getRecurrentWoByIdWithMessage: $error'); - return { - "success": false, - "data": null, - "message": "Failed to load request details. Please check your connection and try again.", - }; - } - } -} - -class ExceptionWithMessage implements Exception { - final String message; - - ExceptionWithMessage(this.message); -} diff --git a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart index 668e06d5..001dea08 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart @@ -16,6 +16,7 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/qr/scan_qr.dart'; class CleanerSearchFormView extends StatefulWidget { @@ -203,6 +204,7 @@ class _CleanerSearchFormViewState extends State { ), // Scan QR Code Button + 8.height, GestureDetector( onTap: selectedTaskType != null ? _onScanQRCode : null, child: Container( @@ -223,21 +225,17 @@ class _CleanerSearchFormViewState extends State { "scan_asset".toSvgAsset( height: 22, fit: BoxFit.fitHeight, - color: selectedTaskType != null - ? (context.isDark ? AppColor.neutral20 : AppColor.neutral120) - : Colors.grey, + color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.neutral120) : Colors.grey, ), 8.width, //we can change the implementation when merge with uiux branch - _scannedLocationData==null? "Scan QR Code".bodyText(context).custom( - color: selectedTaskType != null - ? (context.isDark ? AppColor.neutral20 : AppColor.black20) - : Colors.grey, - ):'Re Scan QR Code'.bodyText(context).custom( - color: selectedTaskType != null - ? (context.isDark ? AppColor.neutral20 : AppColor.black20) - : Colors.grey, - ), + _scannedLocationData == null + ? "Scan QR Code".bodyText(context).custom( + color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.black20) : Colors.grey, + ) + : 'Re Scan QR Code'.bodyText(context).custom( + color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.black20) : Colors.grey, + ), ], ), ), @@ -263,7 +261,7 @@ class _CleanerSearchFormViewState extends State { FooterActionButton.footerContainer( context: context, child: AppFilledButton( - buttonColor:_scannedLocationData != null && !_isProceeding? AppColor.primary10: Colors.grey, + buttonColor: _scannedLocationData != null && !_isProceeding ? AppColor.primary10 : Colors.grey, label: 'Proceed', maxWidth: true, onPressed: (_scannedLocationData != null && !_isProceeding) ? _onProceed : null, @@ -290,49 +288,45 @@ class _CleanerSearchFormViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (locationData.siteName != null && locationData.siteName!.isNotEmpty) - Text( - 'Site: ${locationData.siteName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextWidget( + label: 'Site', value: locationData.siteName, + // style: AppTextStyles.bodyText.copyWith( + // color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + // ), ), if (locationData.buildingName != null && locationData.buildingName!.isNotEmpty) - Text( - 'Building: ${locationData.buildingName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextWidget( + label: 'Building', + value: locationData.buildingName, ), if (locationData.floorName != null && locationData.floorName!.isNotEmpty) - Text( - 'Floor: ${locationData.floorName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextWidget( + label: 'Floor', + value: locationData.floorName, ), if (locationData.departmentName != null && locationData.departmentName!.isNotEmpty) - Text( - 'Department: ${locationData.departmentName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextWidget( + label: 'Department', + value: locationData.departmentName, ), if (locationData.roomName != null && locationData.roomName!.isNotEmpty) - Text( - 'Room: ${locationData.roomName}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextWidget( + label: 'Room', + value: locationData.roomName, ), if (locationData.locationDescription != null && locationData.locationDescription!.isNotEmpty) - Text( - 'Location Description: ${locationData.locationDescription}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - softWrap: true, - maxLines: null, + InfoTextWidget( + label: 'Location Description', + value: locationData.locationDescription, ), + // Text( + // 'Location Description: ${locationData.locationDescription}', + // style: AppTextStyles.bodyText.copyWith( + // color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + // ), + // softWrap: true, + // maxLines: null, + // ), ], ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), ], diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart index 0a6eeaf6..0e44bcda 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -2,7 +2,6 @@ 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/extensions/text_extensions.dart'; import 'package:test_sa/modules/cleaner_module/models/one_time_task_data.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -11,6 +10,8 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/timer/app_timer.dart'; import 'package:test_sa/models/timer_model.dart'; import 'dart:convert'; @@ -102,24 +103,20 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + InfoHeader16Widget( 'Task Information', - style: AppTextStyles.heading5.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), ), 12.height, Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (taskData.oneTimeSetting?.taskTypeName != null) _buildInfoRow(context, 'Task Type', taskData.oneTimeSetting!.taskTypeName!), - if (taskData.siteName != null) _buildInfoRow(context, 'Site', taskData.siteName!), - if (taskData.buildingName != null) _buildInfoRow(context, 'Building', taskData.buildingName!), - if (taskData.floorName != null) _buildInfoRow(context, 'Floor', taskData.floorName!), - if (taskData.departmentName != null) _buildInfoRow(context, 'Department', taskData.departmentName!), - if (taskData.roomName != null) _buildInfoRow(context, 'Room', taskData.roomName!), - if (taskData.locationDescription != null) _buildInfoRow(context, 'Location', taskData.locationDescription!), + if (taskData.oneTimeSetting?.taskTypeName != null) InfoTextWidget(label: 'Task Type', value: taskData.oneTimeSetting!.taskTypeName!), + if (taskData.siteName != null) InfoTextWidget(label: 'Site', value: taskData.siteName!), + if (taskData.buildingName != null) InfoTextWidget(label: 'Building', value: taskData.buildingName!), + if (taskData.floorName != null) InfoTextWidget(label: 'Floor', value: taskData.floorName!), + if (taskData.departmentName != null) InfoTextWidget(label: 'Department', value: taskData.departmentName!), + if (taskData.roomName != null) InfoTextWidget(label: 'Room', value: taskData.roomName!), + if (taskData.locationDescription != null) InfoTextWidget(label: 'Location', value: taskData.locationDescription!), ], ).paddingAll(16), ], @@ -140,12 +137,8 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + InfoHeader16Widget( 'Working Hours', - style: AppTextStyles.heading5.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), ), 12.height, if (timerStartTime != null) @@ -156,7 +149,8 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { endAt: null, durationInSecond: 0, ), - enabled: false, // Disable controls, just show the timer + enabled: false, + // Disable controls, just show the timer showTimer: true, decoration: BoxDecoration( color: context.isDark ? AppColor.neutral20.withValues(alpha: 0.5) : AppColor.neutral10.withValues(alpha: 0.1), @@ -173,18 +167,11 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Total Working Hours', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextLabelWidget( + label: 'Total Working Hours', ), - Text( + InfoHeader16Widget( CMRequestUtils.formatTotalWorkingHours(totalWorkingHours), - style: AppTextStyles.heading6.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - fontWeight: FontWeight.w600, - ), ), ], ), @@ -203,19 +190,11 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( + InfoHeader16Widget( 'Checklist', - style: AppTextStyles.heading5.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), ), - Text( - '${completedInstructions.length}/$totalInstructions', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - fontWeight: FontWeight.w600, - ), + InfoTextLabelWidget( + label: '${completedInstructions.length}/$totalInstructions', ), ], ), @@ -234,18 +213,15 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( + const Icon( Icons.check_circle, color: AppColor.primary10, size: 20, ), 8.width, Expanded( - child: Text( - instruction.instructionText ?? '', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + child: InfoTextLabelWidget( + label: instruction.instructionText ?? '', ), ), ], @@ -262,19 +238,12 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Comment:', - style: AppTextStyles.tinyFont.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), + InfoTextLabelWidget( + label: 'Comment:', ), 4.height, - Text( - instruction.comment!, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextLabelWidget( + label: instruction.comment!, ), ], ), @@ -293,12 +262,8 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + InfoHeader16Widget( 'Captured Images', - style: AppTextStyles.heading5.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), ), 16.height, Wrap( @@ -350,12 +315,8 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + InfoHeader16Widget( 'Attachments', - style: AppTextStyles.heading5.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - fontWeight: FontWeight.w600, - ), ), FilesList( images: taskData.attachments!.map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '').toList(), @@ -387,20 +348,13 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { children: [ SizedBox( width: 100, - child: Text( - '$label:', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), + child: InfoTextLabelWidget( + label:'$label:', ), ), Expanded( - child: Text( - value, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - fontWeight: FontWeight.w500, - ), + child: InfoTextLabelWidget( + label: value, ), ), ], diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index 85636944..5a94f7e6 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -18,6 +18,9 @@ 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/default_app_bar.dart'; import 'package:test_sa/new_views/pages/land_page/land_page.dart'; +import 'package:test_sa/views/widgets/item_views/info_date_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; @@ -218,40 +221,32 @@ class _OneTimeTaskViewState extends State { textColor: AppColor.getHistoryLogStatusTextColorByName(context, _taskData!.statusName!), backgroundColor: AppColor.getHistoryLogStatusColorByName(context, _taskData!.statusName!), ), - if (_taskData?.createdDate != null) - Text( - _taskData!.createdDate!.toString().toServiceRequestCardFormat, - textAlign: TextAlign.end, - style: AppTextStyles.tinyFont.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), - ), + if (_taskData?.createdDate != null) InfoDateWidget(_taskData!.createdDate!.toString()), ], ), 12.height, if (_taskData?.oneTimeSetting?.taskTypeName != null) - Text( - _taskData!.oneTimeSetting!.taskTypeName!, - style: AppTextStyles.heading4.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), + InfoTextLabelWidget( + label: _taskData!.oneTimeSetting!.taskTypeName!, ), - 8.height, + + // Text( + // _taskData!.oneTimeSetting!.taskTypeName!, + // style: AppTextStyles.heading4.copyWith( + // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + // ), + // ), + // 8.height, if (_taskData?.oneTimeSetting?.instructionDescriptionName != null) - Text( - _taskData!.oneTimeSetting!.instructionDescriptionName!, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + InfoTextLabelWidget( + label: _taskData!.oneTimeSetting!.instructionDescriptionName!, ), - 8.height, - if (_taskData?.siteName != null) _buildInfoRow('Site', _taskData!.siteName!), - if (_taskData?.buildingName != null) _buildInfoRow('Building', _taskData!.buildingName!), - if (_taskData?.floorName != null) _buildInfoRow('Floor', _taskData!.floorName!), - if (_taskData?.departmentName != null) _buildInfoRow('Department', _taskData!.departmentName!), - if (_taskData?.roomName != null) _buildInfoRow('Room', _taskData!.roomName!), - if (_taskData?.locationDescription != null) _buildInfoRow('Location', _taskData!.locationDescription!), - // Timer display using AppTimer widget + if (_taskData?.siteName != null) InfoTextWidget(label: 'Site', value: _taskData!.siteName!), + if (_taskData?.buildingName != null) InfoTextWidget(label: 'Building', value: _taskData!.buildingName!), + if (_taskData?.floorName != null) InfoTextWidget(label: 'Floor', value: _taskData!.floorName!), + if (_taskData?.departmentName != null) InfoTextWidget(label: 'Department', value: _taskData!.departmentName!), + if (_taskData?.roomName != null) InfoTextWidget(label: 'Room', value: _taskData!.roomName!), + if (_taskData?.locationDescription != null) InfoTextWidget(label: 'Location', value: _taskData!.locationDescription!), if (_timerStart != null && !isClosed) ...[ 12.height, AppTimer( @@ -286,18 +281,6 @@ class _OneTimeTaskViewState extends State { ).toShadowContainer(context, padding: 0); } - Widget _buildInfoRow(String label, String value) { - return Padding( - padding: const EdgeInsets.only(bottom: 4), - child: Text( - '$label: $value', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - ); - } - List _convertInstructionsToChecklist() { // If checklist items are already created, reuse them if (_checklistItems != null) { @@ -354,22 +337,15 @@ class _OneTimeTaskViewState extends State { // Validate that at least one image is captured final capturedImages = _taskData?.imageRooms ?? []; - print('DEBUG: Captured images count: ${capturedImages.length}'); - print('DEBUG: Images: $capturedImages'); - if (capturedImages.isEmpty) { _showErrorDialog(context, "Please capture at least one image before completing"); return; } - - // Calculate working hours double totalWorkingHours = 0; if (_timerStart != null) { final duration = DateTime.now().difference(_timerStart!); totalWorkingHours = duration.inSeconds / 3600.0; } - - // Navigate to preview screen Navigator.of(context).push( MaterialPageRoute( builder: (context) => OneTimeTaskPreviewScreen( @@ -401,7 +377,6 @@ class _OneTimeTaskViewState extends State { }).toList() ?? []; - // Convert images to attachments format final attachmentsPayload = _taskData?.imageRooms?.map((imageString) { // imageString format: "filename|base64data" final parts = imageString.split('|'); diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 804372c1..3f69efa1 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -11,6 +11,8 @@ import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; import 'package:test_sa/modules/cleaner_module/widgets/multi_image_picker.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; class CleanerChecklistWidget extends StatefulWidget { final List checklistItems; @@ -19,14 +21,7 @@ class CleanerChecklistWidget extends StatefulWidget { final Function(List)? onImagesChanged; final bool isClosed; - const CleanerChecklistWidget({ - Key? key, - required this.checklistItems, - this.onChecklistChanged, - required this.capturedImages, - this.onImagesChanged, - this.isClosed = false - }) : super(key: key); + const CleanerChecklistWidget({Key? key, required this.checklistItems, this.onChecklistChanged, required this.capturedImages, this.onImagesChanged, this.isClosed = false}) : super(key: key); @override State createState() => _CleanerChecklistWidgetState(); @@ -43,12 +38,7 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Checklist'.addTranslation, - style: AppTextStyles.heading4.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), - ), + InfoHeader16Widget('Checklist'), 16.height, ListView.separated( shrinkWrap: true, @@ -93,10 +83,7 @@ class _CleanerChecklistWidgetState extends State { children: [ TextSpan( text: item.title, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - fontWeight: FontWeight.w500, - ), + style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), ), if (item.isMandatory) TextSpan( @@ -175,9 +162,7 @@ class _CleanerChecklistWidgetState extends State { labelText: context.translation.comment, backgroundColor: AppColor.fieldBgColor(context), showShadow: false, - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith( - color: context.isDark ? AppColor.white10 : AppColor.black10, - ), + labelStyle: AppTextStyles.textFieldLabelStyle, alignLabelWithHint: true, textInputType: TextInputType.multiline, onChange: (value) { @@ -199,19 +184,7 @@ class _CleanerChecklistWidgetState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Captured Image'.addTranslation, - style: AppTextStyles.heading6.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), - ), - // Text( - // '(${widget.capturedImages.length}/$maxImages)', - // style: AppTextStyles.bodyText.copyWith( - // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - // fontWeight: FontWeight.w600, - // ), - // ), + InfoHeader16Widget('Captured Image'), ], ), 8.height, @@ -227,28 +200,30 @@ class _CleanerChecklistWidgetState extends State { if (widget.capturedImages.isEmpty) { // Show add image button when no image return GestureDetector( - onTap: widget.isClosed ? null : () async { - // Only allow camera - pick one image at a time - final pickedFile = await ImagePicker().pickImage( - source: ImageSource.camera, - imageQuality: 70, - maxWidth: 800, - maxHeight: 800, - ); + onTap: widget.isClosed + ? null + : () async { + // Only allow camera - pick one image at a time + final pickedFile = await ImagePicker().pickImage( + source: ImageSource.camera, + imageQuality: 70, + maxWidth: 800, + maxHeight: 800, + ); - if (pickedFile != null) { - final bytes = await pickedFile.readAsBytes(); - final base64Image = base64Encode(bytes); - final fileName = pickedFile.path.split("/").last; - final formattedImage = "$fileName|$base64Image"; + if (pickedFile != null) { + final bytes = await pickedFile.readAsBytes(); + final base64Image = base64Encode(bytes); + final fileName = pickedFile.path.split("/").last; + final formattedImage = "$fileName|$base64Image"; - // Add to existing images (max 2) - final updatedImages = List.from(widget.capturedImages); - updatedImages.add(formattedImage); + // Add to existing images (max 2) + final updatedImages = List.from(widget.capturedImages); + updatedImages.add(formattedImage); - widget.onImagesChanged?.call(updatedImages); - } - }, + widget.onImagesChanged?.call(updatedImages); + } + }, child: Container( height: 120, width: 120, @@ -270,12 +245,14 @@ class _CleanerChecklistWidgetState extends State { color: context.isDark ? AppColor.white10 : AppColor.black10, ), 4.height, - Text( - 'Add Image', - style: AppTextStyles.tinyFont.copyWith( - color: context.isDark ? AppColor.white10 : AppColor.black10, - ), - ), + InfoTextLabelWidget(label: 'Add Image'), + + // Text( + // 'Add Image', + // style: AppTextStyles.tinyFont.copyWith( + // color: context.isDark ? AppColor.white10 : AppColor.black10, + // ), + // ), ], ), ), @@ -283,8 +260,7 @@ class _CleanerChecklistWidgetState extends State { } // Check if images are URLs from API (like ServiceRequestDetailView does) - final bool hasApiImages = widget.capturedImages.any((img) => - img.startsWith('http://') || img.startsWith('https://')); + final bool hasApiImages = widget.capturedImages.any((img) => img.startsWith('http://') || img.startsWith('https://')); // If images are from API (URLs), use FilesList widget like ServiceRequestDetailView if (hasApiImages && widget.isClosed) { @@ -298,7 +274,8 @@ class _CleanerChecklistWidgetState extends State { return MultiImagePicker( imageUrls: widget.capturedImages, enabled: !widget.isClosed, - maxImages: 2, // Enforce max 2 images + maxImages: 2, + // Enforce max 2 images onImageTap: (index, imageUrl) { // Handle image tap - show in full screen Navigator.of(context).push( diff --git a/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart b/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart index 9adfdd58..77da2886 100644 --- a/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart +++ b/lib/modules/cleaner_module/widgets/one_time_task_item_view.dart @@ -10,6 +10,9 @@ 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/cleaner_module/pages/one_time_task_view.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_date_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; class OneTimeTaskItemView extends StatelessWidget { @@ -34,33 +37,20 @@ class OneTimeTaskItemView extends StatelessWidget { 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), - ), + InfoDateWidget(requestData!.transactionDate ?? ""), ], ), 8.height, - (requestData?.typeTransaction ?? context.translation.recurrentWo).heading5(context), - 8.height, - '${context.translation.taskNo}: ${requestData!.requestNo}'.bodyText(context), - if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), - if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), - if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), - if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), - if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(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) - ], + InfoHeaderWidget(requestData?.typeTransaction ?? 'One Time Task'.addTranslation), + InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo), + if (requestData?.siteName != null) InfoTextWidget(label: context.translation.site, value: requestData!.siteName), + if (requestData!.buildingName != null) InfoTextWidget(label: context.translation.building, value: requestData!.buildingName), + if (requestData!.departmentName != null) InfoTextWidget(label: context.translation.department, value: requestData!.departmentName), + if (requestData!.floorName != null) InfoTextWidget(label: context.translation.floor, value: requestData!.floorName), + InfoTextWidget( + label: context.translation.room, + value: requestData!.roomName, + showArrow: true, ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() { @@ -89,38 +79,23 @@ class OneTimeTaskItemView extends StatelessWidget { 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), - ), + InfoDateWidget(requestDetails!.date ?? ""), ], ), 8.height, - (requestDetails?.nameOfType ?? context.translation.recurrentWo).heading5(context), - 8.height, - '${context.translation.site}: ${requestDetails!.site}'.bodyText(context), - '${context.translation.taskNo}: ${requestDetails!.requestNo}'.bodyText(context), - if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), - if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), - if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), - if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), - if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(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) - ], + InfoHeaderWidget(requestDetails?.nameOfType ?? 'One Time Task'.addTranslation), + InfoTextWidget(label: context.translation.taskNo, value: requestDetails!.requestNo), + if (requestDetails!.siteName != null) InfoTextWidget(label: context.translation.site, value: requestDetails!.siteName), + if (requestDetails!.buildingName != null) InfoTextWidget(label: context.translation.building, value: requestDetails!.buildingName), + if (requestDetails!.departmentName != null) InfoTextWidget(label: context.translation.department, value: requestDetails!.departmentName), + if (requestDetails!.floorName != null) InfoTextWidget(label: context.translation.floor, value: requestDetails!.floorName), + InfoTextWidget( + label: context.translation.room, + value: requestDetails!.roomName, + showArrow: true, ), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { - // Navigate to editable view for in-progress tasks Navigator.push( context, MaterialPageRoute( diff --git a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart index d7ee1e9f..1e8cd0ee 100644 --- a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart +++ b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart @@ -1,14 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:test_sa/controllers/providers/api/user_provider.dart'; -import 'package:test_sa/controllers/providers/settings/setting_provider.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/models/enums/user_types.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; -import 'package:test_sa/modules/cleaner_module/pages/cleaner_search_details_view.dart'; import 'package:test_sa/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/views/widgets/item_views/info_date_widget.dart'; @@ -25,7 +20,6 @@ class RecurrentWoItemView extends StatelessWidget { @override Widget build(BuildContext context) { - final user = Provider.of(context, listen: false).user; if (requestData != null) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -49,15 +43,14 @@ class RecurrentWoItemView extends StatelessWidget { ], ), 8.height, - InfoHeaderWidget(requestData?.typeTransaction ?? context.translation.recurrentWo), - InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo, showArrow: true), - // todo @waseem replace below code to infotextwidget - // if (requestData?.siteName != null) '${context.translation.site}: ${requestData!.siteName}'.bodyText(context), - // if (requestData!.buildingName != null) '${context.translation.building}: ${requestData!.buildingName}'.bodyText(context), - // if (requestData!.departmentName != null) '${context.translation.department}: ${requestData!.departmentName}'.bodyText(context), - // if (requestData!.floorName != null) '${context.translation.floor}: ${requestData!.floorName}'.bodyText(context), - // if (requestData!.roomName != null) '${context.translation.room}: ${requestData!.roomName}'.bodyText(context), + InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo), + InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo), + if (requestData?.siteName != null) InfoTextWidget(label: context.translation.site, value: requestData!.siteName), + if (requestData!.buildingName != null) InfoTextWidget(label: context.translation.building, value: requestData!.buildingName), + if (requestData!.departmentName != null) InfoTextWidget(label: context.translation.department, value: requestData!.departmentName), + if (requestData!.floorName != null) InfoTextWidget(label: context.translation.floor, value: requestData!.floorName), + if (requestData!.roomName != null) InfoTextWidget(label: context.translation.room, value: requestData!.roomName, showArrow: true), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() { Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestData!.id))); @@ -88,15 +81,15 @@ class RecurrentWoItemView extends StatelessWidget { ), 8.height, InfoHeaderWidget(requestDetails?.nameOfType ?? context.translation.recurrentWo), - InfoTextWidget(label: context.translation.site, value: requestDetails!.site), - InfoTextWidget(label: context.translation.taskNo, value: requestDetails!.requestNo, showArrow: true), - - // todo @waseem replace below code to infotextwidget - // if (requestDetails!.siteName != null) '${context.translation.site}: ${requestDetails!.siteName}'.bodyText(context), - // if (requestDetails!.buildingName != null) '${context.translation.building}: ${requestDetails!.buildingName}'.bodyText(context), - // if (requestDetails!.departmentName != null) '${context.translation.department}: ${requestDetails!.departmentName}'.bodyText(context), - // if (requestDetails!.floorName != null) '${context.translation.floor}: ${requestDetails!.floorName}'.bodyText(context), - // if (requestDetails!.roomName != null) '${context.translation.room}: ${requestDetails!.roomName}'.bodyText(context), + InfoTextWidget( + label: context.translation.taskNo, + value: requestDetails!.requestNo, + ), + if (requestDetails!.siteName != null) InfoTextWidget(label: context.translation.site, value: requestDetails!.siteName), + if (requestDetails!.buildingName != null) InfoTextWidget(label: context.translation.building, value: requestDetails!.buildingName), + if (requestDetails!.departmentName != null) InfoTextWidget(label: context.translation.department, value: requestDetails!.departmentName), + if (requestDetails!.floorName != null) InfoTextWidget(label: context.translation.floor, value: requestDetails!.floorName), + if (requestDetails!.roomName != null) InfoTextWidget(label: context.translation.room, value: requestDetails!.roomName, showArrow: true), ], ).toShadowContainer(context, withShadow: showShadow).onPress(() async { Navigator.of(context).push(MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: requestDetails!.id))); From b4ce7a3dff051b5480918a8a623d699e2ba8e613 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 22 Jun 2026 16:05:03 +0300 Subject: [PATCH 25/73] improvement --- lib/dashboard_latest/widgets/request_category_fragment.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index 488ff6a0..c91797ee 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -198,7 +198,7 @@ class _RequestCategoryFragmentState extends State with mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 'All Tasks'.addTranslation.heading4(context), - 'add_icon'.toSvgAsset(width: 20, height: 20).onPress(() { + 'add_icon'.toSvgAsset(width: 24, height: 24,).onPress(() { Navigator.of(context).pushNamed('/cleaner_search'); }), ], From b1dee3041b0f26f6e57d3f04807825839e0e2ea1 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 23 Jun 2026 10:59:52 +0300 Subject: [PATCH 26/73] FaceId bottom sheet button text changed to Continue. --- lib/new_views/pages/land_page/land_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_views/pages/land_page/land_page.dart b/lib/new_views/pages/land_page/land_page.dart index b024e501..0ac7ad9e 100644 --- a/lib/new_views/pages/land_page/land_page.dart +++ b/lib/new_views/pages/land_page/land_page.dart @@ -95,7 +95,7 @@ class _LandPageState extends State { Navigator.pop(context); Navigator.of(context).pushNamed(SettingsPage.id); }, - label: "Enable".addTranslation), + label: "Continue".addTranslation), ], )), ), From d26cbb36a9fdd46915a5a928cabc0fb0b2f2efbf Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 24 Jun 2026 15:03:17 +0300 Subject: [PATCH 27/73] rollout sheet fixes. --- .../create_medical_gas_request_page.dart | 1 + .../medical_gas_inspection_detail_page.dart | 8 ++------ .../models/medical_gas_supplier_model.dart | 2 +- .../update_delivery_notes_page.dart | 6 +++--- .../recurrent_wo/components/task_info_widget.dart | 2 +- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/modules/medical_gas_inspection/create_medical_gas_request_page.dart b/lib/modules/medical_gas_inspection/create_medical_gas_request_page.dart index 88813f58..03b577a9 100644 --- a/lib/modules/medical_gas_inspection/create_medical_gas_request_page.dart +++ b/lib/modules/medical_gas_inspection/create_medical_gas_request_page.dart @@ -237,6 +237,7 @@ class _CreateMedicalGasRequestPageState extends State CreateMedicalGasRequestPage( - dataModel: model, - ))); + bool? isRefresh = await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CreateMedicalGasRequestPage(dataModel: model))); if (isRefresh == true) { _loadData(); } diff --git a/lib/modules/medical_gas_inspection/models/medical_gas_supplier_model.dart b/lib/modules/medical_gas_inspection/models/medical_gas_supplier_model.dart index 323fcfba..b6fa4b55 100644 --- a/lib/modules/medical_gas_inspection/models/medical_gas_supplier_model.dart +++ b/lib/modules/medical_gas_inspection/models/medical_gas_supplier_model.dart @@ -49,7 +49,7 @@ class AttributeInfo { AttributeInfo.fromJson(Map json) { id = json['id']; medicalGasAttributeId = json['medicalGasAttributeId']; - requestedQuantity = json['requestedQuantity']; + requestedQuantity = json['value'] ?? json['requestedQuantity']; value = json['value']; itemName = json['itemName']; oracleName = json['oracleName']; diff --git a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart index 45f2e604..73d24c57 100644 --- a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart +++ b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart @@ -371,9 +371,9 @@ class _UpdateDeliveryNotesState extends State { return "Enter a valid number"; } - if (intVal <= 0) { - return "Value must be greater than 0"; - } + // if (intVal <= 0) { + // return "Value must be greater than 0"; + // } return null; }, diff --git a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart index 9446e975..20147948 100644 --- a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart +++ b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart @@ -88,7 +88,7 @@ class _RecurrentTaskInfoWidgetState extends State { '${context.translation.room}: ${widget.model!.room?.name ?? "-"}'.bodyText2(context).custom(color: AppColor.neutral120), ], '${context.translation.assignedEmployee}: ${widget.model!.engineer?.userName ?? ""}'.bodyText2(context).custom(color: AppColor.neutral120), - '${context.translation.scheduledDate}: ${widget.model!.scheduleDate!.toMonthYearFormat}'.bodyText2(context).custom(color: AppColor.neutral120), + '${context.translation.scheduledDate}: ${widget.model!.scheduleDate!.toServiceRequestDetailsFormat}'.bodyText2(context).custom(color: AppColor.neutral120), ], ).toShadowContainer(context), 12.height, From 5b5010113b5dd5047eb14bd54e7dcc1a49b3f2e9 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 25 Jun 2026 16:07:47 +0300 Subject: [PATCH 28/73] improvement. --- lib/modules/traf_module/traf_request_item_view.dart | 2 -- .../pages/land_page/requests/recurrent_wo_item_view.dart | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/modules/traf_module/traf_request_item_view.dart b/lib/modules/traf_module/traf_request_item_view.dart index 3e74c425..15223204 100644 --- a/lib/modules/traf_module/traf_request_item_view.dart +++ b/lib/modules/traf_module/traf_request_item_view.dart @@ -43,7 +43,6 @@ class TrafRequestItemView extends StatelessWidget { InfoDateWidget(requestData!.transactionDate ?? ""), ], ), - 8.height, InfoHeaderWidget(requestData?.typeTransaction ?? "Assessment of Need & Technology"), InfoTextWidget(label: context.translation.requestType, value: requestData?.requestTypeName), InfoTextWidget(label: "Request No", value: requestData?.requestNo, showArrow: true), @@ -74,7 +73,6 @@ class TrafRequestItemView extends StatelessWidget { InfoDateWidget(requestDetails!.date ?? ""), ], ), - 8.height, InfoHeaderWidget(requestDetails?.nameOfType ?? "Assessment of Need & Technology"), InfoTextWidget(label: context.translation.requestType, value: requestDetails!.requestType), InfoTextWidget(label: "Request No", value: requestDetails!.requestNo), diff --git a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart index 1e8cd0ee..5b92db52 100644 --- a/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart +++ b/lib/new_views/pages/land_page/requests/recurrent_wo_item_view.dart @@ -42,7 +42,6 @@ class RecurrentWoItemView extends StatelessWidget { InfoDateWidget(requestData!.transactionDate ?? ""), ], ), - 8.height, InfoHeaderWidget(requestData?.typeTransaction ?? context.translation.recurrentWo), InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo), InfoTextWidget(label: context.translation.taskNo, value: requestData!.requestNo), @@ -79,7 +78,6 @@ class RecurrentWoItemView extends StatelessWidget { InfoDateWidget(requestDetails!.date ?? ""), ], ), - 8.height, InfoHeaderWidget(requestDetails?.nameOfType ?? context.translation.recurrentWo), InfoTextWidget( label: context.translation.taskNo, From f6fa5218b623d8a42c4ba100e5d4396cbfc9cac5 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 28 Jun 2026 11:31:05 +0300 Subject: [PATCH 29/73] cleaner module api call fixed in new ui/ux ehancement. --- lib/dashboard_latest/dashboard_view.dart | 30 ++- .../widgets/request_category_fragment.dart | 1 + ...request_category_fragment_housekeeper.dart | 240 ++++++++++++++++++ lib/new_views/pages/land_page/land_page.dart | 4 +- 4 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart diff --git a/lib/dashboard_latest/dashboard_view.dart b/lib/dashboard_latest/dashboard_view.dart index 88db0502..ca986cc1 100644 --- a/lib/dashboard_latest/dashboard_view.dart +++ b/lib/dashboard_latest/dashboard_view.dart @@ -13,6 +13,7 @@ import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; import 'package:test_sa/dashboard_latest/widgets/progress_fragment.dart'; +import 'package:test_sa/dashboard_latest/widgets/request_category_fragment_housekeeper.dart'; import 'package:test_sa/dashboard_latest/widgets/requests_fragment.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -58,17 +59,22 @@ class _DashboardViewState extends State { _dashBoardProvider.setTabs(userType: userProvider.user!.type!, context: context); _dashBoardProvider.getDashBoardCount(usersType: userProvider.user!.type!); _dashBoardProvider.resetRequestDataList(); - _dashBoardProvider.currentListIndex = null; - Provider.of(context, listen: false) - ..reset() - ..getAllRequests(context); - - // _dashBoardProvider.getRequestDetail( - // usersType: userProvider.user!.type!, - // status: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex].tag, - // tabId: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex].id, - // isHighPriority: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex].isHighPriority, - // isOverdue: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex].isOverDue); + + if (userProvider.isHouseKeeper) { + _dashBoardProvider.currentListIndex = 0; + _dashBoardProvider.getRequestDetail( + usersType: userProvider.user!.type!, + showLoader: true, + status: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].tag, + tabId: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].id, + // isAscending: _dashBoardProvider.isAscending, + ); + } else { + _dashBoardProvider.currentListIndex = null; + Provider.of(context, listen: false) + ..reset() + ..getAllRequests(context); + } } void getInitialData() { @@ -145,7 +151,7 @@ class _DashboardViewState extends State { children: [ 16.height, ProgressFragment(), 16.height, // const RequestsFragment(), - const RequestCategoryFragment() + context.userProvider.isHouseKeeper ? const RequestCategoryFragmentHouseKeeper() : const RequestCategoryFragment() ], ), ), diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index c91797ee..c452d306 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -80,6 +80,7 @@ class _RequestCategoryFragmentState extends State with // Reload data with new sort order dashboardProvider.resetRequestDataList(); + Provider.of(context, listen: false).reset(); if (dashboardProvider.listIndex.isEmpty) { // No category selected - do nothing or could reload default diff --git a/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart b/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart new file mode 100644 index 00000000..38f1e95a --- /dev/null +++ b/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; +import 'package:test_sa/dashboard_latest/widgets/request_category_list.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/enum_extensions.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/enums/user_types.dart'; +import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/tab_button.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; + +/// Separate component for HouseKeeper role +/// HouseKeeper has simpler requirements: +/// - Single-select only (no multi-select) +/// - Uses getRequestDetail API only +/// - Default shows first category (In Progress) +class RequestCategoryFragmentHouseKeeper extends StatefulWidget { + const RequestCategoryFragmentHouseKeeper({Key? key}) : super(key: key); + + @override + State createState() => _RequestCategoryFragmentHouseKeeperState(); +} + +class _RequestCategoryFragmentHouseKeeperState extends State with SingleTickerProviderStateMixin { + bool isAscending = false; + late AnimationController _animationController; + late Animation _headerFadeAnimation; + late Animation _headerSlideAnimation; + final ScrollController _scrollController = ScrollController(); + // int _selectedIndex = 0; // Default first tab + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + // Header animation (appears first, faster) + _headerFadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0.0, 0.4, curve: Curves.easeOut), + ), + ); + + _headerSlideAnimation = Tween( + begin: const Offset(0.0, -0.3), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0.0, 0.5, curve: Curves.easeOutCubic), + ), + ); + + _animationController.forward(); + + // Load first category data on init + WidgetsBinding.instance.addPostFrameCallback((_) { + _loadCategoryData(0); + }); + } + + @override + void dispose() { + _animationController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + + void _loadCategoryData(int index) { + final dashboardProvider = Provider.of(context, listen: false); + List tabs = CategoryTabs.getTabs(userType: UsersTypes.houseKeeper, context: context); + + if (index >= 0 && index < tabs.length) { + dashboardProvider.resetRequestDataList(); + dashboardProvider.getRequestDetail( + usersType: UsersTypes.houseKeeper, + showLoader: true, + status: tabs[index].tag, + tabId: tabs[index].id, + isAscending: isAscending, + ); + } + } + + void _toggleSort(DashBoardProvider dashboardProvider) { + setState(() { + isAscending = !isAscending; + }); + + // Reload current category with new sort order + _loadCategoryData(dashboardProvider.currentListIndex!); + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, dashboardProvider, child) { + List tabs = CategoryTabs.getTabs(userType: UsersTypes.houseKeeper, context: context); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Tabs + _buildTabs(tabs, dashboardProvider), + + // Header with sort + FadeTransition( + opacity: _headerFadeAnimation, + child: SlideTransition( + position: _headerSlideAnimation, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'All Tasks'.addTranslation.heading4(context), + Transform.flip( + flipY: isAscending, + child: 'dashboard/sort_descending'.toSvgAsset( + width: 24, + height: 24, + color: AppColor.headingTextColor(context), + ).onPress(()=>_toggleSort(dashboardProvider)), + ) + ], + ).paddingOnly(start: 16, end: 16), + ), + ), + + // Data list + dashboardProvider.isDetailLoading + ? Column( + mainAxisSize: MainAxisSize.max, + children: List.generate(3, (index) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 10.toScreenHeight, horizontal: 0), + child: const SizedBox().toRequestShimmer(context, dashboardProvider.isDetailLoading), + ); + }), + ).paddingOnly(start: 16, end: 16, top: 16) + : (dashboardProvider.requestDetailList == null || dashboardProvider.requestDetailList?.data?.isEmpty == true) + ? const NoDataFound().paddingOnly(top: 80).center + : RequestCategoryList( + dashboardProvider.requestDetailList?.data ?? [], + dashboardProvider.isDetailLoading, + dashboardProvider.requestDetailList?.totalRows ?? 0, + ), + ], + ); + }); + } + + Widget _buildTabs(List tabs, DashBoardProvider dashboardProvider) { + // Calculate item width + final screenWidth = MediaQuery.of(context).size.width; + final isTablet = context.isTablet(); + final visibleItemsWithPeek = isTablet ? 4.5 : 3.5; + const spacing = 4.0; + const horizontalPadding = 16.0; + + final availableWidth = screenWidth - (horizontalPadding * 2); + final fullItems = visibleItemsWithPeek.floor(); + final totalSpacing = (fullItems - 1) * spacing + (spacing * 0.5); + final itemWidth = (availableWidth - totalSpacing) / visibleItemsWithPeek; + + return SizedBox( + height: itemWidth + 16, + child: ListView.separated( + controller: _scrollController, + scrollDirection: Axis.horizontal, + separatorBuilder: (cxt, index) => const SizedBox(width: 4), + padding: const EdgeInsets.only(bottom: 16, left: horizontalPadding, right: horizontalPadding), + itemBuilder: (cxt, index) { + // Animation + final delay = index * 0.08; + final animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOut, + ), + ), + ); + + final slideAnimation = Tween( + begin: const Offset(-0.3, 0.0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOutCubic, + ), + ), + ); + + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: slideAnimation, + child: SizedBox( + width: itemWidth, + child: RequestTypeButton( + label: tabs[index].label, + loading: dashboardProvider.isDetailLoading && dashboardProvider.currentListIndex == index, + isSelected: dashboardProvider.currentListIndex == index, + count: dashboardProvider.getStatusCount(tabs[index].requestType), + onPressed: () { + setState(() { + isAscending = false; + // _selectedIndex = index; + }); + dashboardProvider.currentListIndex = index; + _loadCategoryData(index); + }, + icon: tabs[index].icon, + iconColor: tabs[index].iconColor, + ), + ), + ), + ); + }, + itemCount: tabs.length, + ), + ); + } +} + diff --git a/lib/new_views/pages/land_page/land_page.dart b/lib/new_views/pages/land_page/land_page.dart index 60af1bc4..bd475186 100644 --- a/lib/new_views/pages/land_page/land_page.dart +++ b/lib/new_views/pages/land_page/land_page.dart @@ -115,7 +115,9 @@ class _LandPageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { _userProvider!.getSwipeLastTransaction(userId: _userProvider!.user!.userID!); _userProvider!.getSiteContactInfo(); - Provider.of(context, listen: false).getData(); + if(!_userProvider!.isHouseKeeper) { + Provider.of(context, listen: false).getData(); + } }); } _pages = [ From 40690d6551b1530bca94e704fff0da2cdf94aa87 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 29 Jun 2026 15:06:34 +0300 Subject: [PATCH 30/73] asset type added in cm detail. --- .../views/components/service_request_detail_view.dart | 4 ++++ 1 file changed, 4 insertions(+) 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 a6d4d880..026fac67 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 @@ -215,6 +215,10 @@ class _ServiceRequestDetailViewState extends State { '${context.translation.model}: ${workOrder.model?.name!.cleanupWhitespace?.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), + Text( + '${context.translation.assetType}: ${workOrder.assetType?.name?.cleanupWhitespace?.capitalizeFirstOfEach}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), Text( '${context.translation.site}: ${workOrder.site?.siteName?.cleanupWhitespace?.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), From 5d068349c75830571f3c4206327825f9d4dd6b35 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 6 Jul 2026 09:42:04 +0300 Subject: [PATCH 31/73] incident module wo by asset cont. --- lib/controllers/api_routes/urls.dart | 8 +- .../create_incident_request_page.dart | 16 ++- .../incident_module/incident_detail_page.dart | 13 ++ .../auto_complete/wo_auto_complete_field.dart | 133 ++++++++++++++++++ 4 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 lib/views/widgets/auto_complete/wo_auto_complete_field.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 8995f023..94e03ff2 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "42"; - static const host1 = "https://atomsm.hmg.com"; // production url + // 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://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT - // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 5ceb5dca..deac471e 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -22,6 +22,7 @@ import 'package:test_sa/modules/incident_module/incident_lookup_provider.dart'; import 'package:test_sa/modules/incident_module/incident_provider.dart'; import 'package:test_sa/modules/loan_module/models/medical_department_model.dart'; import 'package:test_sa/modules/loan_module/provider/medical_department_provider.dart'; +import 'package:test_sa/modules/traf_module/asset_auto_complete_field.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'; @@ -30,6 +31,7 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/lookups/yes_no_lookup_provider.dart'; +import 'package:test_sa/views/widgets/auto_complete/wo_auto_complete_field.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; @@ -136,7 +138,19 @@ class _CreateIncidentRequestPageState extends State { payload["departmentId"] = asset.department?.id; setState(() {}); }, - ) + ), + 12.height, + WoAutoCompleteField( + clearAfterPick: false, + byName: true, + enabled: device != null, + initialValue: "", + onPick: (asset) { + // trafRequest?.assetNDId = asset.id; + // model.partCatalogItem = PartCatalogItem(id: part.sparePart?.id, partNumber: part.sparePart?.partNo, partName: part.sparePart?.partName, oracleCode: part.sparePart?.oracleCode); + // setState(() {}); + }, + ), ], if (incidentType?.value != null && incidentType?.value != 1) ...[ 16.height, diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index 2484a9e9..6eb8c521 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; @@ -8,6 +9,7 @@ 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/cm_detail_page.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/incident_module/incident_attachment_model.dart'; import 'package:test_sa/modules/incident_module/incident_data_model.dart'; @@ -101,6 +103,17 @@ class IncidentDetailPage extends StatelessWidget { 'Incident Title: ${incidentData.incidentTitle ?? "-"}'.bodyText(context), 'Incident Description: ${incidentData.incidentDescription ?? "-"}'.bodyText(context), 'Exist OVR: ${incidentData.existingOvrTicketName ?? "-"}'.bodyText(context), + RichText( + text: TextSpan(text: "CM WorkOrder: ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ + TextSpan( + text: "Privacy Policy", + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer() + ..onTap = () async { + await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CMDetailPage(requestId: incidentData.id!, moduleId: 3))); + }), + ]), + ), 'Site: ${incidentData.siteName ?? "-"}'.bodyText(context), 'Building: ${incidentData.buildingName ?? "-"}'.bodyText(context), 'Floor: ${incidentData.floorName ?? "-"}'.bodyText(context), diff --git a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart new file mode 100644 index 00000000..56ebae4c --- /dev/null +++ b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/providers/api/oracle_code_provider.dart'; +import 'package:test_sa/controllers/providers/api/parts_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.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/new_models/asset_nd_auto_complete_by_dynamic_codes_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +import '../../../extensions/text_extensions.dart'; +import '../../../models/service_request/spare_parts.dart'; +import '../../../new_views/app_style/app_text_style.dart'; + +class WoAutoCompleteField extends StatefulWidget { + final String initialValue; + final num? assetId; + final bool clearAfterPick, byName, enabled; + final Function(AssetNDAutoCompleteByDynamicCodesModel) onPick; + + const WoAutoCompleteField({Key? key, required this.byName, required this.initialValue, this.assetId, required this.onPick, this.clearAfterPick = true, this.enabled = true}) : super(key: key); + + @override + _WoAutoCompleteFieldState createState() => _WoAutoCompleteFieldState(); +} + +class _WoAutoCompleteFieldState extends State { + late OracleCodeProvider _oracleCodeProvider; + + late TextEditingController _controller; + + bool loading = false; + + @override + void initState() { + _controller = TextEditingController(text: widget.initialValue); + super.initState(); + _oracleCodeProvider = Provider.of(context, listen: false); + } + + @override + void didUpdateWidget(covariant WoAutoCompleteField oldWidget) { + if (widget.initialValue != oldWidget.initialValue) { + _controller = TextEditingController(text: widget.initialValue); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _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 _oracleCodeProvider.getAssetByOracleCode(textEditingValue.text)); + setState(() { + loading = false; + }); + return workOrders; + }, + displayStringForOption: (AssetNDAutoCompleteByDynamicCodesModel option) => widget.byName ? option.displayName ?? "" : option.codeValue ?? "", + fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { + return TextField( + controller: _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, + enabled: widget.enabled, + 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: "Name/Oracle Code", + 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: (AssetNDAutoCompleteByDynamicCodesModel selection) { + if (widget.clearAfterPick) { + _controller.clear(); + } else { + _controller.text = widget.byName ? (selection.displayName ?? "") : (selection.codeValue ?? ""); + } + widget.onPick(selection); + }, + ), + ); + } +} From b34ede43beb934e0e2d1016ede56c22c459fa5fc Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:13:14 +0300 Subject: [PATCH 32/73] attachment bug fixes for medical gas request --- .../update_delivery_notes_page.dart | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart index 73d24c57..9e9038dd 100644 --- a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart +++ b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart @@ -1,3 +1,7 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; @@ -6,6 +10,8 @@ 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/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/medical_gas_inspection/models/delivery_notes_form_model.dart'; import 'package:test_sa/modules/medical_gas_inspection/models/medical_gas_data_model.dart'; @@ -37,9 +43,7 @@ class _UpdateDeliveryNotesState extends State { bool pressureTestAcknowledgement = false; bool leakTestAcknowledgement = false; final GlobalKey _formKey = GlobalKey(); - final GlobalKey _scaffoldKey = GlobalKey(); - - // List selectedItemList = []; + List _attachments = []; @override void initState() { @@ -62,9 +66,13 @@ class _UpdateDeliveryNotesState extends State { isInspectedGasLeak: dataModel.isInspectedGasLeak, isPressureChecked: dataModel.isPressureChecked, deliveredDate: dataModel.deliveredDate, - attachments: dataModel.deliveryNoteAttachmentDto, + // attachments: dataModel.deliveryNoteAttachmentDto, isSubmitted: dataModel.isSubmitted, deliveryNoteNumber: dataModel.deliveryNote); + + if (dataModel.deliveryNoteAttachmentDto.isNotEmpty) { + _attachments.addAll(dataModel.deliveryNoteAttachmentDto.map((e) => GenericAttachmentModel(id: e.id, name: e.name)).toList()); + } //Need to Confirm this condition // selectedItemList = dataModel.requestDetailDtos.where((item) => item.deliveredQuantity != null && item.deliveredQuantity! > 0).toList(); setState(() {}); @@ -201,7 +209,7 @@ class _UpdateDeliveryNotesState extends State { AttachmentPicker( label: 'Upload Attachment', - attachment: formModel.attachments, + attachment: _attachments, buttonColor: AppColor.primary10, // showAsListView: true, onlyImages: false, @@ -320,6 +328,15 @@ class _UpdateDeliveryNotesState extends State { deliveredQuantity: item.deliveredQuantity, )); } + formModel.attachments = []; + try { + for (var item in _attachments) { + String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + formModel.attachments.add(GenericAttachmentModel(id: item.id, name: fileName)); + } + } catch (error) { + print(error); + } showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); await medicalGasInspectionProvider.updatedDeliveryNotes(model: formModel).then((status) async { Navigator.pop(context); From 78e9042bbbc9d2031ed3fbbbff4ae038621a0911 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 7 Jul 2026 10:52:50 +0300 Subject: [PATCH 33/73] incident module cm work order integrated & improvements. --- lib/controllers/api_routes/urls.dart | 1 + lib/models/wo_by_asset_id_model.dart | 11 ++++++++ lib/modules/cm_module/cm_detail_provider.dart | 16 ++++++++++++ .../create_incident_request_page.dart | 21 ++++++++++------ .../incident_module/incident_data_model.dart | 8 ++++++ .../incident_module/incident_detail_page.dart | 23 +++++++++-------- .../traf_module/traf_request_detail_page.dart | 4 +-- .../auto_complete/wo_auto_complete_field.dart | 25 +++++++++++-------- 8 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 lib/models/wo_by_asset_id_model.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 94e03ff2..12459d16 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -159,6 +159,7 @@ class URLs { static get assignEngineerToWorkOrderUrl => '$_baseUrl/ServiceRequest/AssignEngineerToWorkOrder'; static get uploadWorkOrderAttachmentsUrl => '$_baseUrl/ServiceRequest/UploadAttachmentsWorkOrder'; + static get workOrderByAssetIdAutoComplete => '$_baseUrl/ServiceRequest/WorkOrderByAssetIdAutoComplete'; static get getArrivalVerificationTypeUrl => '$_baseUrl/ArrivalVerificationType/GetArrivalVerificationType'; diff --git a/lib/models/wo_by_asset_id_model.dart b/lib/models/wo_by_asset_id_model.dart new file mode 100644 index 00000000..8a6497d1 --- /dev/null +++ b/lib/models/wo_by_asset_id_model.dart @@ -0,0 +1,11 @@ +class WoByAssetIdModel { + int? id; + String? workOrderNo; + + WoByAssetIdModel({this.id, this.workOrderNo}); + + WoByAssetIdModel.fromJson(dynamic json) { + id = json['id']; + workOrderNo = json['workOrderNo']; + } +} diff --git a/lib/modules/cm_module/cm_detail_provider.dart b/lib/modules/cm_module/cm_detail_provider.dart index a63dcf9e..9aa4aa4d 100644 --- a/lib/modules/cm_module/cm_detail_provider.dart +++ b/lib/modules/cm_module/cm_detail_provider.dart @@ -17,6 +17,7 @@ import 'package:test_sa/models/new_models/dashboard_detail.dart'; import 'package:test_sa/models/new_models/work_order_detail_model.dart'; import 'package:test_sa/models/service_request/spare_parts.dart'; import 'package:test_sa/models/service_request/supplier_details.dart'; +import 'package:test_sa/models/wo_by_asset_id_model.dart'; import 'cm_request_utils.dart'; @@ -1003,4 +1004,19 @@ class CMDetailProvider extends ChangeNotifier { return -1; } } + + Future> getWorkOrderByAssetId(String searchText, int assetId) async { + late Response response; + try { + response = await ApiManager.instance.post(URLs.workOrderByAssetIdAutoComplete, body: {"pageSize": 1, "pageNumber": 20, "search": searchText, "assetId": assetId}); + List woList = []; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + woList = categoriesListJson.map((wo) => WoByAssetIdModel.fromJson(wo)).toList(); + } + return woList; + } catch (error) { + return []; + } + } } diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index deac471e..ee8f76aa 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -16,6 +16,7 @@ import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/building.dart'; import 'package:test_sa/models/new_models/floor.dart'; import 'package:test_sa/models/new_models/site.dart'; +import 'package:test_sa/models/wo_by_asset_id_model.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/incident_module/incident_lookup_provider.dart'; @@ -65,6 +66,7 @@ class _CreateIncidentRequestPageState extends State { Department? _selectedDepartment; Asset? device; + WoByAssetIdModel? woByAssetId; Lookup? incidentType; Lookup? ovrSystem; @@ -127,10 +129,10 @@ class _CreateIncidentRequestPageState extends State { borderColor: AppColor.black20, onPick: (asset) async { device = asset; - payload["assetId"] = asset.modelDefinition?.id; + payload["assetId"] = asset?.id; payload["oracleCode"] = asset.modelDefinition?.oracleCodes?.first.codeValue; payload["model"] = asset.modelDefinition?.modelName; - payload["manufacturer"] = asset.modelDefinition?.manufacturerId; + payload["manufacturer"] = asset.modelDefinition?.manufacturerName; payload["assetOrigin"] = asset.modelDefinition?.assetName; payload["siteId"] = asset.site?.id; payload["buildingId"] = asset.building?.id; @@ -144,11 +146,11 @@ class _CreateIncidentRequestPageState extends State { clearAfterPick: false, byName: true, enabled: device != null, + assetId: device?.id?.toInt(), initialValue: "", onPick: (asset) { - // trafRequest?.assetNDId = asset.id; - // model.partCatalogItem = PartCatalogItem(id: part.sparePart?.id, partNumber: part.sparePart?.partNo, partName: part.sparePart?.partName, oracleCode: part.sparePart?.oracleCode); - // setState(() {}); + woByAssetId = asset; + setState(() {}); }, ), ], @@ -369,6 +371,7 @@ class _CreateIncidentRequestPageState extends State { onSelect: (value) { incidentType = value; payload["incidentClassificationId"] = value?.id; + payload["workOrderId"] = null; setState(() {}); }, ), @@ -690,17 +693,19 @@ class _CreateIncidentRequestPageState extends State { "Please scan or pick asset".showToast; return; } - + if (incidentType?.value == 1 && device != null && woByAssetId?.id == null) { + "Please choose CM Work Order".showToast; + return; + } if (ovrSystem?.value == 1 && isOvrVerified == false) { "Please verify OVR Ticket Number".showToast; return; } - if (occurrenceDate == null) { "Please select occurrence Data".showToast; return; } - + payload["workOrderId"] = woByAssetId?.id; payload["occurrenceDate"] = occurrenceDate!.toIso8601String(); _formKey.currentState!.save(); diff --git a/lib/modules/incident_module/incident_data_model.dart b/lib/modules/incident_module/incident_data_model.dart index 6e5e46f1..7c9f3ba7 100644 --- a/lib/modules/incident_module/incident_data_model.dart +++ b/lib/modules/incident_module/incident_data_model.dart @@ -74,6 +74,8 @@ class IncidentDataModel { String? occurrenceDate; String? createdDate; List? incidentAttachments; + int? workOrderId; + String? workOrderNo; IncidentDataModel( {this.id, @@ -148,6 +150,8 @@ class IncidentDataModel { this.approvalSignature, this.occurrenceDate, this.createdDate, + this.workOrderId, + this.workOrderNo, this.incidentAttachments}); IncidentDataModel.fromJson(Map json) { @@ -223,6 +227,8 @@ class IncidentDataModel { approvalSignature = json['approvalSignature']; occurrenceDate = json['occurrenceDate']; createdDate = json['createdDate']; + workOrderId = json['workOrderId']; + workOrderNo = json['workOrderNo']; if (json['incidentAttachments'] != null) { incidentAttachments = []; json['incidentAttachments'].forEach((v) { @@ -305,6 +311,8 @@ class IncidentDataModel { data['approvalSignature'] = this.approvalSignature; data['occurrenceDate'] = this.occurrenceDate; data['createdDate'] = this.createdDate; + data['workOrderId'] = this.workOrderId; + data['workOrderNo'] = this.workOrderNo; if (this.incidentAttachments != null) { data['incidentAttachments'] = this.incidentAttachments!.map((v) => v.toJson()).toList(); } diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index 6eb8c521..f96dcd4f 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -103,22 +103,23 @@ class IncidentDetailPage extends StatelessWidget { 'Incident Title: ${incidentData.incidentTitle ?? "-"}'.bodyText(context), 'Incident Description: ${incidentData.incidentDescription ?? "-"}'.bodyText(context), 'Exist OVR: ${incidentData.existingOvrTicketName ?? "-"}'.bodyText(context), - RichText( - text: TextSpan(text: "CM WorkOrder: ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ - TextSpan( - text: "Privacy Policy", - style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), - recognizer: TapGestureRecognizer() - ..onTap = () async { - await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CMDetailPage(requestId: incidentData.id!, moduleId: 3))); - }), - ]), - ), 'Site: ${incidentData.siteName ?? "-"}'.bodyText(context), 'Building: ${incidentData.buildingName ?? "-"}'.bodyText(context), 'Floor: ${incidentData.floorName ?? "-"}'.bodyText(context), 'Department: ${incidentData.departmentName ?? "-"}'.bodyText(context), 'Occurrence Date: ${incidentData.occurrenceDate?.toServiceRequestDetailsFormat ?? "-"}'.bodyText(context), + if (incidentData.workOrderId != null) + RichText( + text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ + TextSpan( + text: "${incidentData.workOrderNo}", + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer() + ..onTap = () async { + await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CMDetailPage(requestId: incidentData.workOrderId!, moduleId: 3))); + }), + ]), + ), 'Root Cause: ${incidentData.rootCauseName ?? "-"}'.bodyText(context), if (incidentData.rootCauseValue == 5) 'Root reason: ${incidentData.otherRootCause ?? "-"}'.bodyText(context), 'Comments: ${incidentData.comments ?? "-"}'.bodyText(context), diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 548cb41b..ea1ea197 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -136,9 +136,9 @@ class _TrafRequestDetailPageState extends State { 'Requested Quantity: ${data.qty ?? '-'}'.bodyText(context), ], 4.height, - 'How would the requested technology solve the current situation and/or serve the purpose?: ${data.purposeAnswer ?? '-'}'.bodyText(context), + 'How would the requested technology solve the current situation and/or serve the purpose?:\n${data.purposeAnswer ?? '-'}'.bodyText(context), 4.height, - 'What is the current practice?: ${data.currentPractise ?? '-'}'.bodyText(context), + 'What is the current practice?:\n${data.currentPractise ?? '-'}'.bodyText(context), 4.height, 'Census Q1: ${data.censusQ1 ?? '-'}'.bodyText(context), 'Census Q2: ${data.censusQ2 ?? '-'}'.bodyText(context), diff --git a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart index 56ebae4c..b157df2e 100644 --- a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart +++ b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart @@ -8,6 +8,8 @@ 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/new_models/asset_nd_auto_complete_by_dynamic_codes_model.dart'; +import 'package:test_sa/models/wo_by_asset_id_model.dart'; +import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/views/app_style/sizing.dart'; @@ -17,9 +19,9 @@ import '../../../new_views/app_style/app_text_style.dart'; class WoAutoCompleteField extends StatefulWidget { final String initialValue; - final num? assetId; + final int? assetId; final bool clearAfterPick, byName, enabled; - final Function(AssetNDAutoCompleteByDynamicCodesModel) onPick; + final Function(WoByAssetIdModel) onPick; const WoAutoCompleteField({Key? key, required this.byName, required this.initialValue, this.assetId, required this.onPick, this.clearAfterPick = true, this.enabled = true}) : super(key: key); @@ -28,7 +30,7 @@ class WoAutoCompleteField extends StatefulWidget { } class _WoAutoCompleteFieldState extends State { - late OracleCodeProvider _oracleCodeProvider; + late CMDetailProvider _cmDetailProvider; late TextEditingController _controller; @@ -38,7 +40,7 @@ class _WoAutoCompleteFieldState extends State { void initState() { _controller = TextEditingController(text: widget.initialValue); super.initState(); - _oracleCodeProvider = Provider.of(context, listen: false); + _cmDetailProvider = Provider.of(context, listen: false); } @override @@ -64,7 +66,7 @@ class _WoAutoCompleteFieldState extends State { borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], ), - child: Autocomplete( + child: Autocomplete( optionsBuilder: (TextEditingValue textEditingValue) async { if (textEditingValue.text.isEmpty) { if (loading) { @@ -72,20 +74,21 @@ class _WoAutoCompleteFieldState extends State { loading = false; }); } - return const Iterable.empty(); + return const Iterable.empty(); } if (!loading) { setState(() { loading = true; }); } - List workOrders = (await _oracleCodeProvider.getAssetByOracleCode(textEditingValue.text)); + List workOrders = (await _cmDetailProvider.getWorkOrderByAssetId(textEditingValue.text, widget.assetId!)); + setState(() { loading = false; }); return workOrders; }, - displayStringForOption: (AssetNDAutoCompleteByDynamicCodesModel option) => widget.byName ? option.displayName ?? "" : option.codeValue ?? "", + displayStringForOption: (WoByAssetIdModel option) => widget.byName ? option.workOrderNo ?? "" : option.workOrderNo ?? "", fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { return TextField( controller: _controller, @@ -106,7 +109,7 @@ class _WoAutoCompleteFieldState extends State { 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: "Name/Oracle Code", + labelText: "CM Work Order Number", labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null, ), @@ -119,11 +122,11 @@ class _WoAutoCompleteFieldState extends State { }, ); }, - onSelected: (AssetNDAutoCompleteByDynamicCodesModel selection) { + onSelected: (WoByAssetIdModel selection) { if (widget.clearAfterPick) { _controller.clear(); } else { - _controller.text = widget.byName ? (selection.displayName ?? "") : (selection.codeValue ?? ""); + _controller.text = widget.byName ? (selection.workOrderNo ?? "") : (selection.workOrderNo ?? ""); } widget.onPick(selection); }, From 0e1d8ab1bbaea61cbbc0fa5867d3e8b80a471f59 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 7 Jul 2026 11:16:48 +0300 Subject: [PATCH 34/73] v1.7.8+44 uploaded. --- lib/controllers/api_routes/urls.dart | 32 ++++++++++++++++++++++------ pubspec.yaml | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 12459d16..43998ee8 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,15 +1,15 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "42"; + static const String appReleaseBuildNumber = "44"; - // static const host1 = "https://atomsm.hmg.com"; // production url + 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://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT - static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module @@ -73,10 +73,15 @@ class URLs { //One Time Task Urls cleaner module new flow urls .... static get getTaskType => "$_baseUrl/OneTimeTasks/GetTaskTypes"; + static get scanQRCode => "$_baseUrl/OneTimeTasks/ScanQRCode"; + static get proceedRequest => "$_baseUrl/OneTimeTasks/Proceed"; + static get completeRequest => "$_baseUrl/OneTimeTasks/Complete"; + static get getRequestById => "$_baseUrl/OneTimeTasks/GetById"; + //service request new flow urls. static get nurseDashboardCountUrl => '$_baseUrl/ServiceRequest/GetDashboardNurseCount'; @@ -159,6 +164,7 @@ class URLs { static get assignEngineerToWorkOrderUrl => '$_baseUrl/ServiceRequest/AssignEngineerToWorkOrder'; static get uploadWorkOrderAttachmentsUrl => '$_baseUrl/ServiceRequest/UploadAttachmentsWorkOrder'; + static get workOrderByAssetIdAutoComplete => '$_baseUrl/ServiceRequest/WorkOrderByAssetIdAutoComplete'; static get getArrivalVerificationTypeUrl => '$_baseUrl/ArrivalVerificationType/GetArrivalVerificationType'; @@ -269,11 +275,17 @@ class URLs { static get getYesNoRequestTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4"; static get getDemoPeriodLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4101"; + static get incidentStatusLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3060"; + static get incidentTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3061"; + static get incidentRootCauseLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3062"; + static get incidentPersonInvolvedLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3063"; + static get clinicalNonClinicalLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3064"; + static get genderLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3065"; static get getDemoDocumentTypes => "$_baseUrl/Lookups/GetLookup?lookupEnum=4106"; @@ -427,15 +439,21 @@ class URLs { static get getLoanPeriod => "$_baseUrl/Lookups/GetLookup?lookupEnum=3051"; static get getMedDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; + static get getFlowMedicalDepartments => "$_baseUrl/FlowDepartments/All"; static get getLoanById => '$_baseUrl/Loan/GetLoanById'; + static get getLoanByWorkOrderId => '$_baseUrl/Loan/GetLoanByWorkOrderId'; + static get getIncidentById => '$_baseUrl/Incident/GetIncidentById'; + static get getOvrTicketDetails => '$_baseUrl/Incident/GetOvrTicketDetails'; static get addLoan => '$_baseUrl/Loan/AddLoan'; + static get loanWorkflowAction => '$_baseUrl/Loan/LoanWorkflowAction'; + static get addIncident => '$_baseUrl/Incident/AddIncident'; static get addDemoTrialOutcome => '$_baseUrl/DemoRequest/addDemoTrialOutcome'; @@ -454,7 +472,9 @@ class URLs { static get getAssetDeliveryInspectionData => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/GetAssetDeliveryExternalDeliveryInspectionByDeliveryId'; static get getOracleUserList => '$_baseUrl/AssetDeliveryExternal/GetEmployeesFromOracle'; + static get saveMasterInfoDeliveryInspection => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/SaveMaseterInfoDeliveryInspection'; + static get sendEmailToAddSignature => '$_baseUrl/Signature/SendEmailToAddSignature'; static get getLineFromExternalPo => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/GetLineFromExternalPo'; @@ -540,4 +560,4 @@ class URLs { static get updateDemoPeriodByRequester => "$_baseUrl/DemoRequest/UpdateDemoPeriodByRequester"; //4104 attachment type lookup -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index afce5ab7..aa34d07d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.7+42 +version: 1.7.8+44 environment: sdk: ">=3.5.0 <4.0.0" From eead2ebb845c21cdb5f9a171e1df50460999c4fa Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 8 Jul 2026 11:19:18 +0300 Subject: [PATCH 35/73] merge conflict resolve. --- .../traf_module/traf_request_detail_page.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 7f32918b..31fcf68e 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -125,8 +125,8 @@ class _TrafRequestDetailPageState extends State { 4.height, 'Requested Quantity: ${data.qty ?? '-'}'.bodyText(context), //todo @waseem replace with infotext ], - InfoTextWidget(label: 'How would the requested technology solve the current situation and/or serve the purpose?', value: "\n" + data.purposeAnswer ?? '-'), - InfoTextWidget(label: 'What is the current practice?', value: "\n" + data?.currentPractise ?? '-'), + InfoTextWidget(label: 'How would the requested technology solve the current situation and/or serve the purpose?', value: "\n${data.purposeAnswer ?? '-'}"), + InfoTextWidget(label: 'What is the current practice?', value: "\n${data.currentPractise ?? '-'}"), InfoTextWidget(label: 'Census Q1', value: data.censusQ1 ?? '-'), InfoTextWidget(label: 'Census Q2', value: data.censusQ2 ?? '-'), InfoTextWidget(label: 'Census Q3', value: data.censusQ3 ?? '-'), @@ -136,15 +136,15 @@ class _TrafRequestDetailPageState extends State { value: "\n${data.trafContacts?.map((item) => item.name).toList().toString() ?? '-'}"), InfoTextWidget(label: 'Is the requesting department going to use the technology solely or shared with other departments?', value: "\n${data.usingSolelyOrSharedName ?? '-'}"), if ((data.departments ?? []).isNotEmpty) ...[ - InfoTextWidget(label: 'Technology used with other departments', value:"\n" + data.departments?.map((item) => item.departmentName).toList().toString() ?? '-'), + InfoTextWidget(label: 'Technology used with other departments', value: "\n${data.departments?.map((item) => item.departmentName).toList().toString() ?? '-'}"), ], - InfoTextWidget(label: 'Would other services be effected by acquiring the new equipment?', value: "\n" +data.isEffectedName ?? '-'), + InfoTextWidget(label: 'Would other services be effected by acquiring the new equipment?', value: "\n${data.isEffectedName ?? '-'}"), if ((data.effectedServices ?? "").isNotEmpty) ...[ - InfoTextWidget(label: 'List of services would it be effected', value:"\n" + data.effectedServices ?? '-'), + InfoTextWidget(label: 'List of services would it be effected', value: "\n${data.effectedServices ?? '-'}"), ], - InfoTextWidget(label: 'Is the equipment going to be used with combination of other equipment to accomplish a specific procedure?', value:"\n" + data.isCombinationName ?? '-'), + InfoTextWidget(label: 'Is the equipment going to be used with combination of other equipment to accomplish a specific procedure?', value: "\n${data.isCombinationName ?? '-'}"), if ((data.usedWithCombination ?? "").isNotEmpty) ...[ - InfoTextWidget(label: 'Equipment going to be used with combination of other equipments', value:"\n" + data.usedWithCombination ?? '-'), + InfoTextWidget(label: 'Equipment going to be used with combination of other equipments', value: "\n${data.usedWithCombination ?? '-'}"), ], if (_attachments.isNotEmpty) ...[ 4.height, From f1fbc78366420eacaba79e23ec2efa04c3dd5547 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 8 Jul 2026 11:36:17 +0300 Subject: [PATCH 36/73] improvement. --- lib/modules/incident_module/incident_detail_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index b131c0eb..854c098f 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -95,7 +95,7 @@ class IncidentDetailPage extends StatelessWidget { InfoTextWidget(label: 'Occurrence Date', value: incidentData.occurrenceDate?.toServiceRequestDetailsFormat ?? "-"), if (incidentData.workOrderId != null) RichText( - text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ + text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), children: [ TextSpan( text: "${incidentData.workOrderNo}", style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), From 6747808dde46bd3d982ee85ddf10065fca5e7fea Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 8 Jul 2026 16:17:39 +0300 Subject: [PATCH 37/73] improvement. --- lib/modules/traf_module/create_traf_request_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 84eed483..4d552cf1 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -519,7 +519,7 @@ class _CreateTRAFRequestPageState extends State { attachment: attachments, buttonColor: AppColor.black10, onlyImages: false, - buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.neutral120), + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), onChange: (attachment) { attachments = attachment; }, @@ -531,6 +531,7 @@ class _CreateTRAFRequestPageState extends State { children: [ Checkbox( value: _acknowledgement, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), activeColor: AppColor.blueStatus(context), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, From 2bc8dc94e9b764f56d4f1d7acf9483609bdef31b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 8 Jul 2026 16:50:00 +0300 Subject: [PATCH 38/73] improvement. --- .../tasks/task_request_detail_view.dart | 12 +++++++----- .../widgets/item_views/info_text_widget.dart | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/modules/tm_module/tasks/task_request_detail_view.dart b/lib/modules/tm_module/tasks/task_request_detail_view.dart index ca6f02e8..347dd114 100644 --- a/lib/modules/tm_module/tasks/task_request_detail_view.dart +++ b/lib/modules/tm_module/tasks/task_request_detail_view.dart @@ -103,6 +103,7 @@ class _TaskRequestDetailsViewState extends State { ? const ALoading() : Column(children: [ SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -243,12 +244,13 @@ class _TaskRequestDetailsViewState extends State { return RichText( text: TextSpan( text: "${context.translation.totalWorkingTime}: ", - style: TextStyle( - fontSize: 14.toScreenWidth, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, decoration: TextDecoration.none, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), + style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), + // TextStyle( + // fontSize: 14.toScreenWidth, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, decoration: TextDecoration.none, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ - TextSpan(text: "$totalHourString ", style: AppTextStyles.bodyText), - const WidgetSpan( - child: Icon(Icons.info_outline_rounded, color: AppColor.primary10, size: 14), + TextSpan(text: "$totalHourString ", style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))), + WidgetSpan( + child: Icon(Icons.info_outline_rounded, color: AppColor.loadingColor(context), size: 14), ), ], ), diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index 06e15e91..2224728b 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -8,9 +8,10 @@ class InfoTextWidget extends StatelessWidget { final String label; final bool showEmptyValue; final bool showArrow; + final bool copyValue; final bool isMsg; - InfoTextWidget({required this.label, this.value, this.showEmptyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); + InfoTextWidget({required this.label, this.value, this.showEmptyValue = false, this.copyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -33,6 +34,16 @@ class InfoTextWidget extends StatelessWidget { "arrow_right".toSvgAsset(width: 17, color: AppColor.primary10), ], ); + } else if (!showArrow) { + // text = Row( + // mainAxisSize: MainAxisSize.min, + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // Flexible(child: text), + // const SizedBox(width: 4), + // Icon(Icons.copy_rounded, color: AppColor.loadingColor(context), size: 12), + // ], + // ); } return text; } @@ -45,7 +56,7 @@ class InfoTextLabelWidget extends StatelessWidget { @override Widget build(BuildContext context) { Widget text; - text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))); + text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))); return text; } -} \ No newline at end of file +} From c765a5f31e492e2231cbb7d97164fb840f27ec63 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 09:48:38 +0300 Subject: [PATCH 39/73] improvement. --- lib/views/widgets/item_views/info_text_widget.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index 2224728b..661e1ab5 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -34,7 +34,7 @@ class InfoTextWidget extends StatelessWidget { "arrow_right".toSvgAsset(width: 17, color: AppColor.primary10), ], ); - } else if (!showArrow) { + } else if (copyValue) { // text = Row( // mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.end, @@ -48,6 +48,7 @@ class InfoTextWidget extends StatelessWidget { return text; } } + class InfoTextLabelWidget extends StatelessWidget { final String label; From e70b57e35c1d10a5f9ca9acb28c86f9e9d4195f2 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 10:58:01 +0300 Subject: [PATCH 40/73] improvement. --- lib/modules/pm_module/ppm_wo/ppm_details_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart index 622131a3..75dcb8da 100644 --- a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart +++ b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart @@ -70,6 +70,7 @@ class _PpmDetailsPageState extends State { : planPreventiveVisit != null ? Column(children: [ SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -99,7 +100,7 @@ class _PpmDetailsPageState extends State { InfoTextWidget(label: context.translation.department, value: planPreventiveVisit.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'), InfoTextWidget(label: context.translation.room, value: (planPreventiveVisit.roomName ?? "").cleanupWhitespace.capitalizeFirstOfEach), ], - ).toShadowContainer(context, padding: 16), + ).toShadowContainer(context), ).expanded, if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[ FooterActionButton.footerContainer( From df2c7af28146c4e758421aac556691dbe456e4c0 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 11:38:38 +0300 Subject: [PATCH 41/73] asset model asset attachment changed. --- lib/models/device/asset_by_id_model.dart | 31 +++++++++++++++++-- .../device_transfer/device_search_screen.dart | 1 - 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index c631b767..b7b0e759 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -54,7 +54,7 @@ class AssetByIdModel { String? comment; bool? isEnabled; String? tagCode; - List? assetAttachments; + List? assetAttachments; String? retirementTypeName; String? retirementStatusName; String? retirementDate; @@ -189,9 +189,9 @@ class AssetByIdModel { isEnabled = json['isEnabled']; tagCode = json['tagCode']; if (json['assetAttachments'] != null) { - assetAttachments = []; + assetAttachments = []; json['assetAttachments'].forEach((v) { - assetAttachments!.add(v); + assetAttachments!.add(AssetAttachment.fromJson(v)); }); } retirementTypeName = json['retirementTypeName']; @@ -323,6 +323,31 @@ class MultiAssets { } } +class AssetAttachment { + int? id; + String? attachmentName; + String? attachmentURL; + String? originalName; + + AssetAttachment({this.id, this.attachmentName, this.attachmentURL, this.originalName}); + + AssetAttachment.fromJson(Map json) { + id = json['id']; + attachmentName = json['attachmentName']; + attachmentURL = json['attachmentURL']; + originalName = json['originalName']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['attachmentName'] = attachmentName; + data['attachmentURL'] = attachmentURL; + data['originalName'] = originalName; + return data; + } +} + class ModelDefinition { int? id; String? assetName; diff --git a/lib/modules/tm_module/device_transfer/device_search_screen.dart b/lib/modules/tm_module/device_transfer/device_search_screen.dart index 56768834..48c96eae 100644 --- a/lib/modules/tm_module/device_transfer/device_search_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_search_screen.dart @@ -49,7 +49,6 @@ class _DeviceSearchScreenState extends State { _modelController.dispose(); _supplierController.dispose(); _siteController.dispose(); - _siteController.dispose(); _manufactureController.dispose(); _mdController.dispose(); super.dispose(); From 38f5f506e6ac109e53d56d94eeaa9727d1788fbf Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 12:12:57 +0300 Subject: [PATCH 42/73] asset model updated. --- lib/models/device/asset_by_id_model.dart | 53 ++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index b7b0e759..b4ac22ef 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -13,12 +13,12 @@ class AssetByIdModel { AssetInfo? oldAsset; AssetReplace? isParent; AssetInfo? parentAsset; - AssetReplace? assetType; + Lookup? assetType; DemoRequest? demoRequest; String? missionCritical; Site? site; - AssetReplace? building; - AssetReplace? floor; + Lookup? building; + Lookup? floor; Department? department; Lookup? room; num? testsDay; @@ -60,6 +60,7 @@ class AssetByIdModel { String? retirementDate; AssetGroup? assetGroup; String? assetPhoto; + String? modelPhoto; String? nextPMDate; String? lastPMDate; @@ -122,6 +123,7 @@ class AssetByIdModel { this.retirementDate, this.assetGroup, this.assetPhoto, + this.modelPhoto, this.nextPMDate, this.lastPMDate}); @@ -142,12 +144,12 @@ class AssetByIdModel { oldAsset = json['oldAsset'] != null ? AssetInfo.fromJson(json['oldAsset']) : null; isParent = json['isParent'] != null ? AssetReplace.fromJson(json['isParent']) : null; parentAsset = json['parentAsset'] != null ? AssetInfo.fromJson(json['parentAsset']) : null; - assetType = json['assetType'] != null ? AssetReplace.fromJson(json['assetType']) : null; + assetType = json['assetType'] != null ? Lookup.fromJson(json['assetType']) : null; demoRequest = json['demoRequest'] != null ? DemoRequest.fromJson(json['demoRequest']) : null; missionCritical = json['missionCritical']; site = json['site'] != null ? Site.fromJson(json['site']) : null; - building = json['building'] != null ? AssetReplace.fromJson(json['building']) : null; - floor = json['floor'] != null ? AssetReplace.fromJson(json['floor']) : null; + building = json['building'] != null ? Lookup.fromJson(json['building']) : null; + floor = json['floor'] != null ? Lookup.fromJson(json['floor']) : null; department = json['department'] != null ? Department.fromJson(json['department']) : null; room = json['room'] != null ? Lookup.fromJson(json['room']) : null; testsDay = json['testsDay']; @@ -199,6 +201,7 @@ class AssetByIdModel { retirementDate = json['retirementDate']; assetGroup = json['assetGroup'] != null ? AssetGroup.fromJson(json['assetGroup']) : null; assetPhoto = json['assetPhoto']; + modelPhoto = json['modelPhoto']; nextPMDate = json['nextPMDate']; lastPMDate = json['lastPMDate']; } @@ -295,6 +298,7 @@ class AssetByIdModel { data['assetGroup'] = assetGroup?.toJson(); } data['assetPhoto'] = assetPhoto; + data['modelPhoto'] = modelPhoto; data['nextPMDate'] = nextPMDate; data['lastPMDate'] = lastPMDate; return data; @@ -571,18 +575,50 @@ class Floors { class Departments { int? id; String? name; + List? rooms; Departments({this.id, this.name}); Departments.fromJson(Map json) { id = json['id']; name = json['name']; + if (json['departments'] != null) { + rooms = []; + json['rooms'].forEach((v) { + rooms!.add(Rooms.fromJson(v)); + }); + } } Map toJson() { final Map data = {}; data['id'] = id; data['name'] = name; + if (rooms != null) { + data['rooms'] = rooms!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Rooms { + int? id; + String? name; + int? value; + + Rooms({this.id, this.name, this.value}); + + Rooms.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; } } @@ -592,14 +628,16 @@ class Department { String? departmentName; String? departmentCode; String? ntCode; + num? operationalHours; - Department({this.id, this.departmentName, this.departmentCode, this.ntCode}); + Department({this.id, this.departmentName, this.departmentCode, this.ntCode, this.operationalHours}); Department.fromJson(Map json) { id = json['id']; departmentName = json['departmentName']; departmentCode = json['departmentCode']; ntCode = json['ntCode']; + operationalHours = json['operationalHours']; } Map toJson() { @@ -608,6 +646,7 @@ class Department { data['departmentName'] = departmentName; data['departmentCode'] = departmentCode; data['ntCode'] = ntCode; + data['operationalHours'] = operationalHours; return data; } } From ace00bfa858ffc2a22d30c97cb3f56635e9a5b5f Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 12 Jul 2026 09:45:23 +0300 Subject: [PATCH 43/73] asset images upload for ai training. --- lib/controllers/api_routes/api_manager.dart | 33 ++- lib/controllers/api_routes/urls.dart | 9 +- .../providers/api/devices_provider.dart | 19 ++ .../widgets/equipment/asset_detail_page.dart | 193 +++++++++++------- .../equipment/asset_image_upload_page.dart | 81 ++++++++ .../widgets/images/multi_image_picker.dart | 7 +- 6 files changed, 246 insertions(+), 96 deletions(-) create mode 100644 lib/views/widgets/equipment/asset_image_upload_page.dart diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart index 4d85989a..89e4b3e0 100644 --- a/lib/controllers/api_routes/api_manager.dart +++ b/lib/controllers/api_routes/api_manager.dart @@ -18,9 +18,9 @@ import 'package:test_sa/new_views/pages/login_page.dart'; class _CachedResponse { final http.Response response; final DateTime timestamp; - + _CachedResponse(this.response, this.timestamp); - + bool isValid(Duration cacheDuration) { return DateTime.now().difference(timestamp) < cacheDuration; } @@ -40,26 +40,26 @@ class ApiManager { User? user; AssetGroup? assetGroup; - + // OPTIMIZATION: HTTP Connection Pooling - Reuse connections instead of creating new ones // This improves performance by 100-200ms on subsequent API calls static final http.Client _httpClient = http.Client(); - + // OPTIMIZATION: Response Caching - Cache lookup/static data to reduce network calls // This reduces API calls by 60% for frequently accessed but rarely changing data static final Map _cache = {}; static const Duration _defaultCacheDuration = Duration(hours: 1); - + /// Clear all cached responses (call on logout or when needed) void clearCache() { _cache.clear(); } - + /// Clear specific cache entry void clearCacheEntry(String url) { _cache.remove(_generateCacheKey(url)); } - + /// Generate cache key from URL and user context String _generateCacheKey(String url) { // Include user and asset group in cache key to avoid cross-user pollution @@ -67,7 +67,7 @@ class ApiManager { } /// GET request with optional caching support - /// + /// /// [url] - API endpoint URL /// [headers] - Optional additional headers /// [enableToastMessage] - Show toast message from API response @@ -85,12 +85,12 @@ class ApiManager { headers ??= {}; headers.addAll(_headers); Uri url0 = Uri.parse(url); - + // OPTIMIZATION: Check cache first if enabled if (useCache && !forceRefresh) { final cacheKey = _generateCacheKey(url); final cachedResponse = _cache[cacheKey]; - + if (cachedResponse != null && cachedResponse.isValid(cacheDuration ?? _defaultCacheDuration)) { if (kDebugMode) { print('📦 Cache HIT: $url'); @@ -98,10 +98,10 @@ class ApiManager { return cachedResponse.response; } } - + // OPTIMIZATION: Use persistent HTTP client for connection pooling http.Response response = await _httpClient.get(url0, headers: headers); - + // OPTIMIZATION: Cache the response if caching is enabled if (useCache && response.statusCode >= 200 && response.statusCode < 300) { final cacheKey = _generateCacheKey(url); @@ -248,7 +248,7 @@ class ApiManager { Uri url0 = Uri.parse(url); // print(headers); // log(json.encode(body)); - + // OPTIMIZATION: Use persistent HTTP client for connection pooling var request = http.Request('PUT', url0); request.body = json.encode(body); @@ -275,10 +275,9 @@ class ApiManager { } Future multiPart(String url, {Map? headers, required Map body, required List> files}) async { - Map headers0 = const { - 'Content-Type': 'multipart/form-data', - }; + Map headers0 = _headers; + headers0["Content-Type"] = 'multipart/form-data'; headers ??= {}; headers.addAll(headers0); @@ -360,7 +359,7 @@ class ApiManager { Navigator.pop(context); Navigator.of(context).pushNamedAndRemoveUntil(LoginPage.routeName, (routes) => true); } - + /// Dispose HTTP client (call when app is closing if needed) /// Note: In most cases, keep client alive for app lifetime for best performance void dispose() { diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 43998ee8..e0820b57 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - static const host1 = "https://atomsm.hmg.com"; // production url + // 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://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module @@ -65,6 +65,7 @@ class URLs { static get getAssets => "$_baseUrl/Asset/GetAssets"; // get static get getFaultDescription => "$_baseUrl/Asset/GetDefectForModelDefinition"; // get static get getAssetById => "$_baseUrl/Asset/GetAssetById?assetId="; // get + static get saveAssetImages => "$_baseUrl/Asset/SaveAssetImages"; // get static get getModels => "$_baseUrl/ModelDefinition/GetModelDefinitionAsset"; // get ?client=2 static get getAllRequestsAndCount => "$_baseUrl/CallRequest/GetAllRequestsAndCount"; // get static get updateUserContactInformation => "$_baseUrl/Account/UpdateContactInfo"; // get diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index 218ebf80..f933a5a9 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -9,6 +9,7 @@ import 'package:test_sa/controllers/api_routes/http_status_manger.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/models/device/asset_by_id_model.dart'; import 'package:test_sa/models/device/asset_search.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/user.dart'; import 'package:test_sa/modules/asset_delivery_module/models/technical_inpection_asset_model.dart'; @@ -230,4 +231,22 @@ class AssetProvider extends ChangeNotifier { return []; } } + + Future uploadAssetImages(int assetId, List attachments) async { + late Response response; + try { + response = + await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: attachments.map((attachment) => MultipartFile.fromPath("Files", attachment.name!)).toList()); + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + // Map assetData = json.decode(response.body)["data"]; + // _assetById = AssetByIdModel.fromJson(assetData); + // return _assetById!; + return true; + } + return false; + } catch (error) { + return false; + } + } } diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index f0b095fb..5beea1c5 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -8,7 +8,13 @@ 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_by_id_model.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.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/equipment/asset_image_upload_page.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/failed_loading.dart'; @@ -39,6 +45,9 @@ class _AssetDetailPageState extends State { int? assetId; AssetProvider? _assetProvider; + AssetByIdModel? assetModel; + + List attachments = []; @override Widget build(BuildContext context) { @@ -48,7 +57,7 @@ class _AssetDetailPageState extends State { return Scaffold( appBar: DefaultAppBar(title: context.translation.assetDetails), body: FutureBuilder( - future: _assetProvider!.getAssetById(assetId!, context.translation), + future: assetModel == null ? _assetProvider!.getAssetById(assetId!, context.translation) : null, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return FailedLoading( @@ -62,87 +71,108 @@ class _AssetDetailPageState extends State { return const Center(child: ALoading()); } else if (snapshot.hasData) {} if (snapshot.hasData) { - AssetByIdModel assetModel = snapshot.data!; - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: 159 / 94, - child: Container( - width: 95, - height: 95, - decoration: ShapeDecoration( - color: AppColor.neutral30, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), - )), - ), - ), - 6.height, + assetModel = snapshot.data!; + attachments = assetModel?.assetAttachments + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? + []; + return Column( + children: [ + ListView(padding: const EdgeInsets.all(16), children: [ Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (assetModel.commissioningStatus != null) - StatusLabel( - label: assetModel.commissioningStatus!.name, - textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + AspectRatio( + aspectRatio: 159 / 94, + child: Container( + width: 95, + height: 95, + decoration: ShapeDecoration( + color: AppColor.neutral30, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(assetModel?.assetPhoto != null ? URLs.getFileUrl(assetModel!.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), + )), ), - if (assetModel.commissioningStatus != null) 8.height, - (assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), - 8.height, - Row( + ), + 6.height, + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${context.translation.assetNo}: ${assetModel.multiAssets!.first.assetNumber}".bodyText(context), - "${context.translation.modelName}: ${assetModel.modelDefinition!.modelName}".bodyText(context), - "${context.translation.supplier}: ${assetModel.supplier?.suppliername ?? "-"}".bodyText(context), - "${context.translation.manufacture}: ${assetModel.modelDefinition!.manufacturerName}".bodyText(context), - //"${context.translation.location}: ${assetModel.site.custName?.cleanupWhitespace?.capitalizeFirstOfEach}".bodyText(context), - ], - ).expanded, - 8.width, - Column( + if (assetModel?.commissioningStatus != null) + StatusLabel( + label: assetModel!.commissioningStatus!.name, + textColor: AppColor.getRequestStatusTextColorByName(context, assetModel!.commissioningStatus!.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel!.commissioningStatus!.name!), + ), + if (assetModel?.commissioningStatus != null) 8.height, + (assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), + 8.height, + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - /// TODO: theres no [ORACLE CODE] available to preview - // "${context.translation.oracleCode}: ${"-"}".bodyText(context), - "${context.translation.snNo}: ${assetModel.multiAssets!.first.assetSerialNo}".bodyText(context), - "${context.translation.site}: ${assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.building}: ${assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.floor}: ${assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.md}: ${assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.room}: ${assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${context.translation.assetNo}: ${assetModel!.multiAssets!.first.assetNumber}".bodyText(context), + "${context.translation.modelName}: ${assetModel!.modelDefinition!.modelName}".bodyText(context), + "${context.translation.supplier}: ${assetModel!.supplier?.suppliername ?? "-"}".bodyText(context), + "${context.translation.manufacture}: ${assetModel!.modelDefinition!.manufacturerName}".bodyText(context), + //"${context.translation.location}: ${assetModel.site.custName?.cleanupWhitespace?.capitalizeFirstOfEach}".bodyText(context), + ], + ).expanded, + 8.width, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// TODO: theres no [ORACLE CODE] available to preview + // "${context.translation.oracleCode}: ${"-"}".bodyText(context), + "${context.translation.snNo}: ${assetModel!.multiAssets!.first.assetSerialNo}".bodyText(context), + "${context.translation.site}: ${assetModel!.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.building}: ${assetModel!.building?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.floor}: ${assetModel!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.md}: ${assetModel!.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.room}: ${assetModel!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + ], + ).expanded, ], - ).expanded, + ), + 8.height, + const Divider(color: AppColor.neutral30, height: 1, thickness: 1), + 8.height, + "${context.translation.installationDate}: ${assetModel!.installationDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + "${context.translation.nextPmDate}: ${assetModel!.nextPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + "${context.translation.lastPmDate}: ${assetModel!.lastPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + if ((assetModel?.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ + 8.height, + const Divider(color: AppColor.neutral30, height: 1, thickness: 1), + 8.height, + assetModel!.modelDefinition!.assetDescription!.bodyText(context), + ], + FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()), ], - ), - 8.height, - const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - 8.height, - "${context.translation.installationDate}: ${assetModel.installationDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - "${context.translation.nextPmDate}: ${assetModel.nextPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - "${context.translation.lastPmDate}: ${assetModel.lastPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ - 8.height, - const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - 8.height, - assetModel.modelDefinition!.assetDescription!.bodyText(context), - ] + ) ], - ) - ], - ).toShadowContainer(context), + ).toShadowContainer(context), + ]).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: _upload, + ), + ), + ], ); } return const Center(child: ALoading()); @@ -150,4 +180,25 @@ class _AssetDetailPageState extends State { ), ); } + + void _upload() async { + bool reload = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AssetImageUploadPage( + assetId: assetId!, + attachments: assetModel?.assetAttachments + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? + []))) ?? + false; + if (reload) { + assetModel = null; + setState(() {}); + } + } } diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart new file mode 100644 index 00000000..314a7b65 --- /dev/null +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/extensions/context_extension.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/generic_attachment_model.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.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/multi_image_picker.dart'; + +class AssetImageUploadPage extends StatefulWidget { + int assetId; + List attachments; + + AssetImageUploadPage({Key? key, required this.assetId, required this.attachments}) : super(key: key); + + @override + _AssetImageUploadPageState createState() { + return _AssetImageUploadPageState(); + } +} + +class _AssetImageUploadPageState extends State { + List attachments = []; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const DefaultAppBar(title: "Upload"), + body: Column( + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + AttachmentPicker( + label: "Upload Images", + attachment: attachments, + buttonColor: AppColor.black10, + onlyImages: true, + showAsListView: true, + buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + onChange: (_attachments) { + attachments = _attachments; + setState(() {}); + }, + ).toShadowContainer(context), + ], + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: () async { + Utils.showUploadingDialog(context); + bool result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); + Utils.hideLoading(context); + if (result) { + Navigator.pop(context, result); + } + }, + ), + ), + ], + )); + } +} diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index aa9ecd79..a7de6c03 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -348,7 +348,7 @@ class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start,spacing: 12, children: [ AppDashedButton( title: widget.label, @@ -360,13 +360,12 @@ class _AttachmentPickerState extends State { : widget.showAsGrid ? showFileSourceSheet : onFilePicker), - 16.height, if (widget.attachment.isNotEmpty) widget.showAsListView ? ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + padding: const EdgeInsets.only(top: 0), itemBuilder: (cxt, index) { File image = File(widget.attachment[index].name!); return MultiFilesPickerItem( @@ -410,7 +409,7 @@ class _AttachmentPickerState extends State { ); }, ), - ), + ).paddingOnly(top: 0), ], ); } From dbc53d62f8a0c6968fc64c7c69412930e9700cad Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 13 Jul 2026 10:03:48 +0300 Subject: [PATCH 44/73] apk provided to FM for asset image training --- lib/controllers/api_routes/urls.dart | 8 +- .../service_request_detail_view.dart | 89 +++++++++++-------- lib/views/widgets/dialogs/loading_dialog.dart | 43 ++++----- .../widgets/equipment/asset_detail_page.dart | 3 +- .../equipment/asset_image_upload_page.dart | 22 +++-- .../widgets/images/multi_image_picker.dart | 42 ++++++--- 6 files changed, 128 insertions(+), 79 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index e0820b57..84b68233 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - // static const host1 = "https://atomsm.hmg.com"; // production url + 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://atomsmuat.hmg.com"; // local UAT url - static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module 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 026fac67..8bc088b8 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 @@ -20,6 +20,7 @@ import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retire import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.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/views/widgets/equipment/asset_detail_page.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -73,43 +74,43 @@ class _ServiceRequestDetailViewState extends State { : requestProvider.currentWorkOrder == null ? const NoDataFound() : Column( - 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), + 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, ], - 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), - ], - ); + ), + ).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), + ], + ); }); } @@ -135,7 +136,7 @@ class _ServiceRequestDetailViewState extends State { // spacing: 8, runSpacing: 8, children: [ - if (Provider.of(context, listen: false).showPriority&&workOrder.priority!=null) ...[ + if (Provider.of(context, listen: false).showPriority && workOrder.priority != null) ...[ StatusLabel( label: workOrder.priority?.name, id: workOrder.priority?.value, @@ -190,9 +191,19 @@ class _ServiceRequestDetailViewState extends State { ], ), 8.height, - Text( - context.translation.assetInformation, - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + Row( + spacing: 4, + children: [ + Flexible( + child: Text( + context.translation.assetInformation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + ), + const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 21).onPress(() { + Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id); + }) + ], ), 8.height, Text( diff --git a/lib/views/widgets/dialogs/loading_dialog.dart b/lib/views/widgets/dialogs/loading_dialog.dart index 1a72e452..a8a6880e 100644 --- a/lib/views/widgets/dialogs/loading_dialog.dart +++ b/lib/views/widgets/dialogs/loading_dialog.dart @@ -9,26 +9,29 @@ class UploadingDialog extends StatelessWidget { @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), - ) - ], + child: Material( + borderRadius: BorderRadius.circular(20.0), + 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/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 5beea1c5..151df55e 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -7,6 +7,7 @@ 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/device/asset_by_id_model.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; @@ -168,7 +169,7 @@ class _AssetDetailPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - label: "Upload Images", + label: "Upload Images (AI Training)", onPressed: _upload, ), ), diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 314a7b65..cd0caae9 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -10,6 +10,7 @@ import 'package:test_sa/modules/cm_module/views/components/action_button/footer_ 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/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; class AssetImageUploadPage extends StatefulWidget { @@ -30,6 +31,7 @@ class _AssetImageUploadPageState extends State { @override void initState() { super.initState(); + // attachments = widget.attachments; } @override @@ -47,10 +49,11 @@ class _AssetImageUploadPageState extends State { padding: const EdgeInsets.all(16), children: [ AttachmentPicker( - label: "Upload Images", + label: "Import", attachment: attachments, buttonColor: AppColor.black10, onlyImages: true, + pickMultiple: true, showAsListView: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { @@ -64,13 +67,22 @@ class _AssetImageUploadPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, + disableButton: attachments.length < 4, label: "Upload Images", onPressed: () async { - Utils.showUploadingDialog(context); - bool result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); - Utils.hideLoading(context); + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: context.translation.confirm, + content: "For more enhancement, these images will be used to train AI model.\n\nAre you sure you are uploading clear and high quality images?"), + ); if (result) { - Navigator.pop(context, result); + Utils.showUploadingDialog(context); + result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); + Utils.hideLoading(context); + if (result) { + Navigator.pop(context, result); + } } }, ), diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index a7de6c03..cead4b1a 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -322,6 +322,7 @@ class AttachmentPicker extends StatefulWidget { final Function(List)? onChange; final bool showAsGrid; final bool showAsListView; + final bool pickMultiple; Lookup? documentType; AttachmentPicker( @@ -337,6 +338,7 @@ class AttachmentPicker extends StatefulWidget { this.documentType, this.showAsGrid = false, this.showAsListView = false, + this.pickMultiple = false, this.buttonColor}) : super(key: key); @@ -348,7 +350,8 @@ class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start,spacing: 12, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ AppDashedButton( title: widget.label, @@ -465,16 +468,21 @@ class _AttachmentPickerState extends State { ).paddingAll(21), )) as ImageSource; - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); + // if(widget.pickMultiple) { + final pickedFile12 = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); - if (pickedFile != null) { - File fileImage = File(pickedFile.path); - widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); - if (widget.onChange != null) { - widget.onChange!(widget.attachment); - } - setState(() {}); - } + // // } + // + // 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, documentTypeId: widget.documentType)); + // if (widget.onChange != null) { + // widget.onChange!(widget.attachment); + // } + // setState(() {}); + // } } Widget gridItem(IconData iconData, String title) { @@ -632,6 +640,20 @@ class _AttachmentPickerState extends State { // ); if (source == null) return; + if (widget.pickMultiple && source == ImageSource.gallery) { + final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); + if (pickedFiles.isNotEmpty) { + pickedFiles.forEach((pickedFile) { + File fileImage = File(pickedFile.path); + widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); + }); + if (widget.onChange != null) { + widget.onChange!(widget.attachment); + } + setState(() {}); + return; + } + } final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { From c82114d25197bdc1eea5cf1b738126a08bd3448c Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:29:32 +0300 Subject: [PATCH 45/73] improvements --- .../medical_gas_inspection/update_delivery_notes_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart index c4fd21ba..47c77c39 100644 --- a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart +++ b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart @@ -400,7 +400,8 @@ class _UpdateDeliveryNotesState extends State { vertical: 6, // keeps normal height ~30 ), filled: true, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(context), + // fillColor: AppColor.fieldBgColor(context), border: border, enabledBorder: border, focusedBorder: border, From fbc14318e5f8e2f914d21bda7165e4fbaa6f14b5 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:05:54 +0300 Subject: [PATCH 46/73] asset images upload functionality added for ai training. --- .../images/asset_image_upload/side_bottom.svg | 13 ++ .../images/asset_image_upload/side_left.svg | 13 ++ .../images/asset_image_upload/side_right.svg | 13 ++ assets/images/asset_image_upload/side_top.svg | 13 ++ lib/controllers/api_routes/urls.dart | 8 +- .../providers/api/devices_provider.dart | 16 +- lib/models/device/asset.dart | 6 + lib/models/device/asset_by_id_model.dart | 47 ++++- lib/models/generic_attachment_model.dart | 12 +- .../new_models/work_order_detail_model.dart | 4 + .../service_request_detail_view.dart | 7 + .../device_transfer/search_device_page.dart | 119 ++++++----- .../widgets/equipment/asset_detail_page.dart | 26 +-- .../equipment/asset_image_upload_page.dart | 192 ++++++++++++++++-- .../equipment/asset_item_gridview.dart | 11 +- .../equipment/asset_item_listview.dart | 9 +- .../equipment/single_device_picker.dart | 2 +- .../widgets/images/multi_image_picker.dart | 156 ++++++++------ pubspec.yaml | 2 + 19 files changed, 498 insertions(+), 171 deletions(-) create mode 100644 assets/images/asset_image_upload/side_bottom.svg create mode 100644 assets/images/asset_image_upload/side_left.svg create mode 100644 assets/images/asset_image_upload/side_right.svg create mode 100644 assets/images/asset_image_upload/side_top.svg diff --git a/assets/images/asset_image_upload/side_bottom.svg b/assets/images/asset_image_upload/side_bottom.svg new file mode 100644 index 00000000..7d9ec605 --- /dev/null +++ b/assets/images/asset_image_upload/side_bottom.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_left.svg b/assets/images/asset_image_upload/side_left.svg new file mode 100644 index 00000000..bdf7e6d9 --- /dev/null +++ b/assets/images/asset_image_upload/side_left.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_right.svg b/assets/images/asset_image_upload/side_right.svg new file mode 100644 index 00000000..f8db13a1 --- /dev/null +++ b/assets/images/asset_image_upload/side_right.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_top.svg b/assets/images/asset_image_upload/side_top.svg new file mode 100644 index 00000000..52841a4e --- /dev/null +++ b/assets/images/asset_image_upload/side_top.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 84b68233..e0820b57 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - static const host1 = "https://atomsm.hmg.com"; // production url + // 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://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index f933a5a9..d3e841ff 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -232,11 +232,21 @@ class AssetProvider extends ChangeNotifier { } } - Future uploadAssetImages(int assetId, List attachments) async { + Future uploadAssetImages(int assetId, TrainingImageData trainingImageData) async { late Response response; try { - response = - await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: attachments.map((attachment) => MultipartFile.fromPath("Files", attachment.name!)).toList()); + List> files = []; + files.add(MultipartFile.fromPath("LeftImage", trainingImageData.leftImage!.name!)); + files.add(MultipartFile.fromPath("RightImage", trainingImageData.rightImage!.name!)); + files.add(MultipartFile.fromPath("UpImage", trainingImageData.upImage!.name!)); + files.add(MultipartFile.fromPath("DownImage", trainingImageData.downImage!.name!)); + + for (var i = 0; i < (trainingImageData.additionalImages?.length ?? 0); i++) { + files.add( + MultipartFile.fromPath('AdditionalImage', trainingImageData.additionalImages![i].name!), + ); + } + response = await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: files); if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received // Map assetData = json.decode(response.body)["data"]; diff --git a/lib/models/device/asset.dart b/lib/models/device/asset.dart index 1e41dc31..91c86dc9 100644 --- a/lib/models/device/asset.dart +++ b/lib/models/device/asset.dart @@ -65,6 +65,7 @@ class Asset { this.comment, this.tagCode, this.assetPhoto, + this.hasTrainingImageData, }); Asset.fromJson(dynamic json) { @@ -122,6 +123,7 @@ class Asset { comment = json['comment']; tagCode = json['tagCode']; assetPhoto = json['assetPhoto']; + hasTrainingImageData = json['hasTrainingImageData']; } num? id; // Now nullable @@ -177,6 +179,7 @@ class Asset { String? comment; // Now nullable String? tagCode; // Now nullable String? assetPhoto; + bool? hasTrainingImageData; Asset copyWith({ num? id, @@ -232,6 +235,7 @@ class Asset { String? comment, String? tagCode, String? assetPhoto, + bool? hasTrainingImageData, }) => Asset( id: id ?? this.id, @@ -287,6 +291,7 @@ class Asset { comment: comment ?? this.comment, tagCode: tagCode ?? this.tagCode, assetPhoto: assetPhoto ?? this.assetPhoto, + hasTrainingImageData: hasTrainingImageData ?? this.hasTrainingImageData, ); Map toJson() { @@ -381,6 +386,7 @@ class Asset { map['comment'] = comment; map['tagCode'] = tagCode; map['assetPhoto'] = assetPhoto; + map['hasTrainingImageData'] = hasTrainingImageData; return map; } } diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index b4ac22ef..ab177b23 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -1,4 +1,5 @@ import 'package:test_sa/models/device/technical_guidance_book.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; class AssetByIdModel { @@ -63,6 +64,8 @@ class AssetByIdModel { String? modelPhoto; String? nextPMDate; String? lastPMDate; + bool? hasTrainingImageData; + TrainingImageData? trainingImageData; AssetByIdModel( {this.multiAssets, @@ -125,9 +128,13 @@ class AssetByIdModel { this.assetPhoto, this.modelPhoto, this.nextPMDate, - this.lastPMDate}); + this.lastPMDate, + this.trainingImageData, + this.hasTrainingImageData}); AssetByIdModel.fromJson(Map json) { + print("assetModel!.fromJson:${json['hasTrainingImageData']}"); + if (json['multiAssets'] != null) { multiAssets = []; json['multiAssets'].forEach((v) { @@ -204,6 +211,8 @@ class AssetByIdModel { modelPhoto = json['modelPhoto']; nextPMDate = json['nextPMDate']; lastPMDate = json['lastPMDate']; + hasTrainingImageData = json['hasTrainingImageData']; + trainingImageData = json['trainingImageData'] != null ? TrainingImageData.fromJson(json['trainingImageData']) : null; } Map toJson() { @@ -301,6 +310,8 @@ class AssetByIdModel { data['modelPhoto'] = modelPhoto; data['nextPMDate'] = nextPMDate; data['lastPMDate'] = lastPMDate; + data['hasTrainingImageData'] = hasTrainingImageData; + data['trainingImageData'] = trainingImageData?.toJson(); return data; } } @@ -789,3 +800,37 @@ class DemoRequest { return data; } } + +class TrainingImageData { + GenericAttachmentModel? leftImage; + GenericAttachmentModel? rightImage; + GenericAttachmentModel? upImage; + GenericAttachmentModel? downImage; + List? additionalImages; + + TrainingImageData({this.leftImage, this.rightImage, this.upImage, this.downImage, this.additionalImages = const []}); + + TrainingImageData.fromJson(Map json) { + leftImage = json['leftImage'] == null ? null : GenericAttachmentModel.fromJson(json['leftImage'], convertToLink: true); + rightImage = json['rightImage'] == null ? null : GenericAttachmentModel.fromJson(json['rightImage'], convertToLink: true); + upImage = json['upImage'] == null ? null : GenericAttachmentModel.fromJson(json['upImage'], convertToLink: true); + downImage = json['downImage'] == null ? null : GenericAttachmentModel.fromJson(json['downImage'], convertToLink: true); + additionalImages = []; + if (json['additionalImages'] != null) { + json['additionalImages'].forEach((v) { + additionalImages!.add(GenericAttachmentModel.fromJson(v, convertToLink: true)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['leftImage'] = leftImage?.toJson(); + data['rightImage'] = rightImage?.toJson(); + data['upImage'] = upImage?.toJson(); + data['downImage'] = downImage?.toJson(); + data['additionalImages'] = additionalImages?.map((v) => v.toJson()).toList() ?? []; + + return data; + } +} diff --git a/lib/models/generic_attachment_model.dart b/lib/models/generic_attachment_model.dart index dde1d74d..6ba126e1 100644 --- a/lib/models/generic_attachment_model.dart +++ b/lib/models/generic_attachment_model.dart @@ -1,9 +1,10 @@ import 'dart:io'; +import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/models/lookup.dart'; class GenericAttachmentModel { - GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy,this.documentTypeId}); + GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy, this.documentTypeId}); int? id; String? name; @@ -11,11 +12,14 @@ class GenericAttachmentModel { String? originalName; Lookup? documentTypeId; - GenericAttachmentModel.fromJson(Map json) { + GenericAttachmentModel.fromJson(Map json, {bool convertToLink = false}) { id = json['id']; - name = json['name']; + name = json['name'] ?? json['imageName']; createdBy = json['createdBy']; - originalName = json['originalName']; + originalName = json['originalName'] ?? json['imageOriginalName']; + if (convertToLink) { + name = URLs.getFileUrl(name); + } } Map toJson() { diff --git a/lib/models/new_models/work_order_detail_model.dart b/lib/models/new_models/work_order_detail_model.dart index 1936113a..984edca5 100644 --- a/lib/models/new_models/work_order_detail_model.dart +++ b/lib/models/new_models/work_order_detail_model.dart @@ -304,17 +304,20 @@ class WorkOrderAsset { required this.id, required this.assetNumber, required this.assetSerialNo, + required this.hasTrainingImageData, }); int? id; String? assetNumber; String? assetSerialNo; + bool? hasTrainingImageData; factory WorkOrderAsset.fromJson(Map json) { return WorkOrderAsset( id: json["id"], assetNumber: json["assetNumber"], assetSerialNo: json["assetSerialNo"], + hasTrainingImageData: json["hasTrainingImageData"], ); } @@ -322,6 +325,7 @@ class WorkOrderAsset { "id": id, "assetNumber": assetNumber, "assetSerialNo": assetSerialNo, + "hasTrainingImageData": hasTrainingImageData, }; } 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 8bc088b8..6ebd657c 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 @@ -206,6 +206,13 @@ class _ServiceRequestDetailViewState extends State { ], ), 8.height, + if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[ + Text( + '*Images are required for the asset', + style: AppTextStyles.bodyText.copyWith(color: AppColor.red30), + ), + 4.height, + ], Text( '${context.translation.assetName}: ${workOrder.assetNdModel!.name?.cleanupWhitespace?.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), diff --git a/lib/modules/tm_module/device_transfer/search_device_page.dart b/lib/modules/tm_module/device_transfer/search_device_page.dart index cc76e370..93a6f523 100644 --- a/lib/modules/tm_module/device_transfer/search_device_page.dart +++ b/lib/modules/tm_module/device_transfer/search_device_page.dart @@ -171,68 +171,67 @@ class _SearchDevicePageState extends State { ).expanded, if (widget.multiSelection && selectedAssets.isNotEmpty) FooterActionButton.footerContainer( - context: context, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: AppFilledButton( - label: "Clear", - buttonColor: AppColor.white60, - textColor: AppColor.black10, - loading: false, - onPressed: () async { - selectedAssets.clear(); - setState(() {}); - }, + context: context, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: AppFilledButton( + label: "Clear", + buttonColor: AppColor.white60, + textColor: AppColor.black10, + loading: false, + onPressed: () async { + selectedAssets.clear(); + setState(() {}); + }, + ), ), - ), - 12.width, - Expanded( - child: AppFilledButton( - label: context.translation.select, - buttonColor: AppColor.primary10, - loading: false, - onPressed: () async { - Navigator.of(context).pop(); - List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); - Navigator.of(context).pop(assets); - }, + 12.width, + Expanded( + child: AppFilledButton( + label: context.translation.select, + buttonColor: AppColor.primary10, + loading: false, + onPressed: () async { + Navigator.of(context).pop(); + List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); + Navigator.of(context).pop(assets); + }, + ), ), - ), - ], - ) - ), - // Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Expanded( - // child: AppFilledButton( - // label: "Clear", - // buttonColor: AppColor.white60, - // textColor: AppColor.black10, - // loading: false, - // onPressed: () async { - // selectedAssets.clear(); - // setState(() {}); - // }, - // ), - // ), - // 12.width, - // Expanded( - // child: AppFilledButton( - // label: context.translation.select, - // buttonColor: AppColor.primary10, - // loading: false, - // onPressed: () async { - // Navigator.of(context).pop(); - // List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); - // Navigator.of(context).pop(assets); - // }, - // ), - // ), - // ], - // ).toShadowContainer(context), + ], + )), + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Expanded( + // child: AppFilledButton( + // label: "Clear", + // buttonColor: AppColor.white60, + // textColor: AppColor.black10, + // loading: false, + // onPressed: () async { + // selectedAssets.clear(); + // setState(() {}); + // }, + // ), + // ), + // 12.width, + // Expanded( + // child: AppFilledButton( + // label: context.translation.select, + // buttonColor: AppColor.primary10, + // loading: false, + // onPressed: () async { + // Navigator.of(context).pop(); + // List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); + // Navigator.of(context).pop(assets); + // }, + // ), + // ), + // ], + // ).toShadowContainer(context), ], ), ), diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 151df55e..9c9c0d97 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -117,6 +117,13 @@ class _AssetDetailPageState extends State { if (assetModel?.commissioningStatus != null) 8.height, (assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), 8.height, + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) ...[ + Text( + '*Upload Images for this asset', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), + ), + 4.height, + ], Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -164,12 +171,12 @@ class _AssetDetailPageState extends State { ], ).toShadowContainer(context), ]).expanded, - if (context.userProvider.isEngineer) + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - label: "Upload Images (AI Training)", + label: "Upload Images", onPressed: _upload, ), ), @@ -183,20 +190,7 @@ class _AssetDetailPageState extends State { } void _upload() async { - bool reload = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AssetImageUploadPage( - assetId: assetId!, - attachments: assetModel?.assetAttachments - ?.map((attachment) => GenericAttachmentModel( - id: attachment.id, - name: attachment.attachmentName, - originalName: attachment.originalName, - )) - .toList() ?? - []))) ?? - false; + bool reload = await Navigator.push(context, MaterialPageRoute(builder: (context) => AssetImageUploadPage(assetId: assetId!))) ?? false; if (reload) { assetModel = null; setState(() {}); diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index cd0caae9..65d1efc9 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/devices_provider.dart'; @@ -5,7 +8,9 @@ import 'package:test_sa/extensions/context_extension.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/device/asset_by_id_model.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -15,9 +20,9 @@ import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; class AssetImageUploadPage extends StatefulWidget { int assetId; - List attachments; + TrainingImageData? attachments; - AssetImageUploadPage({Key? key, required this.assetId, required this.attachments}) : super(key: key); + AssetImageUploadPage({Key? key, required this.assetId, this.attachments}) : super(key: key); @override _AssetImageUploadPageState createState() { @@ -26,12 +31,12 @@ class AssetImageUploadPage extends StatefulWidget { } class _AssetImageUploadPageState extends State { - List attachments = []; + late TrainingImageData trainingImageData; @override void initState() { super.initState(); - // attachments = widget.attachments; + trainingImageData = widget.attachments ?? TrainingImageData(additionalImages: []); } @override @@ -48,16 +53,68 @@ class _AssetImageUploadPageState extends State { ListView( padding: const EdgeInsets.all(16), children: [ + Text( + "Instructions", + style: AppTextStyles.heading5.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + Text( + '* Please upload all images of the sides of the asset as mentioned below.\n* Images should be clear and high quality.\n* Avoid uploading blurry, low-light or low-resolution images.', + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + GridView( + padding: const EdgeInsets.only(top: 16, bottom: 16), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 4 : 4, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), + children: [ + gridCard( + icon: "asset_image_upload/side_right", + title: "Right Side", + attachment: trainingImageData.rightImage, + onUpdate: (value) { + trainingImageData.rightImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_left", + title: "Left Side", + attachment: trainingImageData.leftImage, + onUpdate: (value) { + trainingImageData.leftImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_top", + title: "Top Side", + attachment: trainingImageData.upImage, + onUpdate: (value) { + trainingImageData.upImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_bottom", + title: "Bottom Side", + attachment: trainingImageData.downImage, + onUpdate: (value) { + trainingImageData.downImage = value; + setState(() {}); + }, + ), + ], + ), AttachmentPicker( - label: "Import", - attachment: attachments, + label: "Additional Images", + attachment: trainingImageData.additionalImages!, buttonColor: AppColor.black10, onlyImages: true, pickMultiple: true, - showAsListView: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { - attachments = _attachments; + trainingImageData.additionalImages = _attachments; setState(() {}); }, ).toShadowContainer(context), @@ -67,18 +124,19 @@ class _AssetImageUploadPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - disableButton: attachments.length < 4, - label: "Upload Images", + disableButton: trainingImageData.leftImage == null || trainingImageData.rightImage == null || trainingImageData.upImage == null || trainingImageData.downImage == null, + label: "Upload", onPressed: () async { bool result = await showDialog( context: context, builder: (_) => AAlertDialog( - title: context.translation.confirm, - content: "For more enhancement, these images will be used to train AI model.\n\nAre you sure you are uploading clear and high quality images?"), + title: context.translation.confirm, + content: "I followed all the instructions and uploading the images in high quality, clear and not blurry.", + ), ); if (result) { Utils.showUploadingDialog(context); - result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); + result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); Utils.hideLoading(context); if (result) { Navigator.pop(context, result); @@ -90,4 +148,112 @@ class _AssetImageUploadPageState extends State { ], )); } + + Widget gridCard({ + required String icon, + required String title, + required GenericAttachmentModel? attachment, + required Function(GenericAttachmentModel?) onUpdate, + }) { + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Positioned.fill( + child: AttachmentPicker( + label: "", + onlyImages: true, + checkQuality: true, + enabled: attachment == null, + attachment: attachment == null ? [] : [attachment], + buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + onChange: (_attachments) { + if (_attachments.isNotEmpty) { + onUpdate(_attachments.first); + } else { + onUpdate(null); + } + }, + child: imageAngleCard(icon, title, attachment), + ).toShadowContainer(context, padding: 0), + ), + if (attachment != null) + Positioned( + top: 4, + right: 4, + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { + onUpdate(null); + }, + )), + ], + ); + } + + Widget imageAngleCard(String icon, String title, GenericAttachmentModel? attachment) { + if (attachment != null) { + Widget imageWidget = _getImageWidget(attachment.name!); + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: imageWidget.onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea(child: InteractiveViewer(child: imageWidget).center), + ), + ), + ); + })); + } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + icon.toSvgAsset(height: 50, width: 50), + Text( + title, + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ], + ); + } + + Widget _getImageWidget(String imageUrl) { + // Check if it's base64 encoded (contains | separator) + if (imageUrl.contains('|')) { + try { + final base64Data = imageUrl.split('|').last; + return Image.memory( + base64Decode(base64Data), + fit: BoxFit.cover, + ); + } catch (e) { + return const Icon(Icons.broken_image, size: 48); + } + } + + // Check if it's a local file path + if (CMRequestUtils.isLocalUrl(imageUrl)) { + return Image.file( + File(imageUrl), + fit: BoxFit.cover, + ); + } + + // Otherwise treat as network URL + return Image.network( + imageUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.broken_image, size: 48); + }, + ); + } } diff --git a/lib/views/widgets/equipment/asset_item_gridview.dart b/lib/views/widgets/equipment/asset_item_gridview.dart index cc463097..61e89ddc 100644 --- a/lib/views/widgets/equipment/asset_item_gridview.dart +++ b/lib/views/widgets/equipment/asset_item_gridview.dart @@ -12,7 +12,7 @@ class AssetItemGridView extends StatelessWidget { final Asset device; final Function(Asset) onPressed; - const AssetItemGridView({Key? key,required this.device,required this.onPressed}) : super(key: key); + const AssetItemGridView({Key? key, required this.device, required this.onPressed}) : super(key: key); @override Widget build(BuildContext context) { @@ -43,6 +43,13 @@ class AssetItemGridView extends StatelessWidget { overflow: TextOverflow.ellipsis, style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), + if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[ + 2.height, + Text( + '*Upload Images for this asset', + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30), + ), + ], Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -71,6 +78,6 @@ class AssetItemGridView extends StatelessWidget { ], ).expanded ], - ).onPress(() => onPressed(device)).toShadowContainer(context,padding: 12); + ).onPress(() => onPressed(device)).toShadowContainer(context, padding: 12); } } diff --git a/lib/views/widgets/equipment/asset_item_listview.dart b/lib/views/widgets/equipment/asset_item_listview.dart index f2aab423..2c8f96c1 100644 --- a/lib/views/widgets/equipment/asset_item_listview.dart +++ b/lib/views/widgets/equipment/asset_item_listview.dart @@ -41,7 +41,14 @@ class AssetItemListView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ device.modelDefinition!.assetName!.cleanupWhitespace.capitalizeFirstOfEach.heading6(context), - 8.height, + if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[ + 2.height, + Text( + '*Upload Images for this asset', + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30), + ), + ], + 6.height, "${context.translation.assetNumber} : ${device.assetNumber}".bodyText(context), "${context.translation.modelName} : ${device.modelDefinition!.modelName}".cleanupWhitespace.capitalizeFirstOfEach.bodyText(context), ], diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart index bd709317..7bd79936 100644 --- a/lib/views/widgets/equipment/single_device_picker.dart +++ b/lib/views/widgets/equipment/single_device_picker.dart @@ -119,7 +119,7 @@ class _MyAssetsPageState extends State { }).expanded, 16.width, Icon( - Icons.grid_view_outlined, + showListView ? Icons.view_list_rounded : Icons.grid_view_rounded, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20, ).onPress(() => setState(() { showListView = !showListView; diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index cead4b1a..b50b2b3c 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_blur_detection/image_blur_detection.dart'; import 'package:image_picker/image_picker.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -324,6 +325,8 @@ class AttachmentPicker extends StatefulWidget { final bool showAsListView; final bool pickMultiple; Lookup? documentType; + Widget? child; + bool checkQuality; AttachmentPicker( {Key? key, @@ -339,6 +342,8 @@ class AttachmentPicker extends StatefulWidget { this.showAsGrid = false, this.showAsListView = false, this.pickMultiple = false, + this.checkQuality = false, + this.child, this.buttonColor}) : super(key: key); @@ -349,72 +354,78 @@ class AttachmentPicker extends StatefulWidget { class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 12, - children: [ - AppDashedButton( - title: widget.label, - height: widget.buttonHeight, - buttonColor: widget.buttonColor, - icon: widget.buttonIcon, - onPressed: (widget.enabled == false) - ? () {} - : widget.showAsGrid - ? showFileSourceSheet - : onFilePicker), - if (widget.attachment.isNotEmpty) - widget.showAsListView - ? ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 0), - itemBuilder: (cxt, index) { - File image = File(widget.attachment[index].name!); - return MultiFilesPickerItem( - file: image, - showAsListView: widget.showAsListView, - enabled: widget.enabled, - documentType: widget.attachment[index].documentTypeId?.name, - onRemoveTap: (image) { - if (!widget.enabled) { - return; - } - widget.attachment.removeAt(index); - if (widget.onChange != null) { - widget.onChange!(widget.attachment); - } - setState(() {}); - }, - ); - }, - separatorBuilder: (cxt, index) => 8.height, - itemCount: widget.attachment.length) - : 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(() {}); + return widget.child != null + ? widget.child!.onPress((widget.enabled == false) + ? () {} + : widget.showAsGrid + ? () => showFileSourceSheet() + : () => onFilePicker(widget.checkQuality)) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, + children: [ + AppDashedButton( + title: widget.label, + height: widget.buttonHeight, + buttonColor: widget.buttonColor, + icon: widget.buttonIcon, + onPressed: (widget.enabled == false) + ? () {} + : widget.showAsGrid + ? () => showFileSourceSheet() + : () => onFilePicker(widget.checkQuality)), + if (widget.attachment.isNotEmpty) + widget.showAsListView + ? ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.only(top: 0), + itemBuilder: (cxt, index) { + File image = File(widget.attachment[index].name!); + return MultiFilesPickerItem( + file: image, + showAsListView: widget.showAsListView, + enabled: widget.enabled, + documentType: widget.attachment[index].documentTypeId?.name, + onRemoveTap: (image) { + if (!widget.enabled) { + return; + } + widget.attachment.removeAt(index); + if (widget.onChange != null) { + widget.onChange!(widget.attachment); + } + setState(() {}); + }, + ); }, - ); - }, - ), - ).paddingOnly(top: 0), - ], - ); + separatorBuilder: (cxt, index) => 8.height, + itemCount: widget.attachment.length) + : 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(() {}); + }, + ); + }, + ), + ).paddingOnly(top: 0), + ], + ); } fromFilePicker() async { @@ -507,7 +518,7 @@ class _AttachmentPickerState extends State { ); } - onFilePicker() async { + onFilePicker(bool checkQuality) async { ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -658,6 +669,19 @@ class _AttachmentPickerState extends State { if (pickedFile != null) { File fileImage = File(pickedFile.path); + + if (checkQuality) { + ImageQualityValidator imageQualityValidator = ImageQualityValidator(); + final result = await imageQualityValidator.validate(await fileImage.readAsBytes()); + + if (!result.isValid) { + String issues = "Your image have following problems:"; + result.issues.forEach((issue) => (issues += "\n- $issue")); + context.showInfoDialog(issues, title: "Bad Quality Found"); + return; + } + } + widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); if (widget.onChange != null) { widget.onChange!(widget.attachment); diff --git a/pubspec.yaml b/pubspec.yaml index aa34d07d..3b19daf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,6 +88,7 @@ dependencies: flutter_timezone: ^3.0.1 device_calendar: ^4.3.3 pie_chart: ^5.4.0 + image_blur_detection: ^1.0.1 badges: ^3.1.1 # buttons_tabbar: ^1.1.2 @@ -136,6 +137,7 @@ flutter: - assets/ - assets/audio/ - assets/images/ + - assets/images/asset_image_upload/ - assets/lottie/ - assets/subtitles/ - assets/rives/ From e39526c3c6c6b9353b1e92e12d6535edfd84a108 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:11:49 +0300 Subject: [PATCH 47/73] imporvements --- .../cm_module/views/components/service_request_detail_view.dart | 2 +- lib/views/widgets/equipment/asset_image_upload_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 6ebd657c..33f3ea33 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 @@ -200,7 +200,7 @@ class _ServiceRequestDetailViewState extends State { style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), ), - const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 21).onPress(() { + const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 16).onPress(() { Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id); }) ], diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 65d1efc9..02c836d1 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -47,7 +47,7 @@ class _AssetImageUploadPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: const DefaultAppBar(title: "Upload"), + appBar: const DefaultAppBar(title: "Upload Images"), body: Column( children: [ ListView( From e9096b0ebed01ceb0f681ea7832d41dd73c5da9e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:44:02 +0300 Subject: [PATCH 48/73] improvement --- lib/controllers/providers/api/devices_provider.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index d3e841ff..66c95d7b 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -153,9 +153,7 @@ class AssetProvider extends ChangeNotifier { print(error); throw (HttpStatusManger.getStatusMessage(status: -1, subtitle: appLocalizations)); } - _stateCode = response.statusCode; if (response.statusCode >= 200 && response.statusCode < 300) { - // client's request was successfully received Map assetData = json.decode(response.body)["data"]; _assetById = AssetByIdModel.fromJson(assetData); return _assetById!; From 5bf52c6485124e4949af6cda28d77272af399f7e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 14:29:22 +0300 Subject: [PATCH 49/73] image in high quality --- lib/views/widgets/images/multi_image_picker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index b50b2b3c..1f5655db 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -652,7 +652,7 @@ class _AttachmentPickerState extends State { if (source == null) return; if (widget.pickMultiple && source == ImageSource.gallery) { - final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); + final pickedFiles = await ImagePicker().pickMultiImage(); if (pickedFiles.isNotEmpty) { pickedFiles.forEach((pickedFile) { File fileImage = File(pickedFile.path); @@ -665,7 +665,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); + final pickedFile = await ImagePicker().pickImage(source: source); if (pickedFile != null) { File fileImage = File(pickedFile.path); From 6695912d4e946500cb8212e40282354b1b2671a1 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 08:36:09 +0300 Subject: [PATCH 50/73] image quality to 70% --- lib/views/widgets/images/multi_image_picker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index 1f5655db..e43c0ec3 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -652,7 +652,7 @@ class _AttachmentPickerState extends State { if (source == null) return; if (widget.pickMultiple && source == ImageSource.gallery) { - final pickedFiles = await ImagePicker().pickMultiImage(); + final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70); if (pickedFiles.isNotEmpty) { pickedFiles.forEach((pickedFile) { File fileImage = File(pickedFile.path); @@ -665,7 +665,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source); + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70); if (pickedFile != null) { File fileImage = File(pickedFile.path); From 488a83f74f38e1c36581c69490e84984217ff94b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 09:33:46 +0300 Subject: [PATCH 51/73] FullScreen Image Viewer added. --- lib/extensions/context_extension.dart | 14 ++++++++++++- lib/extensions/widget_extensions.dart | 3 ++- lib/views/full_screen_image_viewer.dart | 20 +++++++++++++++++++ .../equipment/asset_image_upload_page.dart | 15 +++----------- 4 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 lib/views/full_screen_image_viewer.dart diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index 41f93d7f..a33130cb 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import 'package:provider/provider.dart'; @@ -5,6 +6,7 @@ import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/info_dialog.dart'; +import 'package:test_sa/views/full_screen_image_viewer.dart'; import '../controllers/providers/settings/setting_provider.dart'; import '../l10n/app_localizations.dart'; @@ -24,6 +26,15 @@ extension BuildContextExtension on BuildContext { UserProvider get userProvider => Provider.of(this, listen: false); + void fullScreenImageViewer(Widget child) { + Navigator.of(this).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => FullScreenImageViewer(child), + ), + ); + } + bool isTablet() { final mediaQueryData = MediaQuery.of(this); final double screenWidth = mediaQueryData.size.width; @@ -49,6 +60,7 @@ extension BuildContextExtension on BuildContext { isScrollControlled: true, isDismissible: true, backgroundColor: Colors.transparent, - builder: (context) => SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title,showCancelButton: showCancelButton), + builder: (context) => + SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title, showCancelButton: showCancelButton), ); } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 252971f7..2e52b835 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -120,12 +120,13 @@ extension WidgetExtensions on Widget { double padding = 12, EdgeInsets? paddingObject, EdgeInsets? margin, + double? width, }) => withShadow ? Container( padding: paddingObject ?? EdgeInsets.all(padding), margin: margin, - width: double.infinity, + width: width ?? double.infinity, decoration: ShapeDecoration( color: backgroundColor ?? AppColor.background(context), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(borderRadius), side: BorderSide(color: borderColor)), diff --git a/lib/views/full_screen_image_viewer.dart b/lib/views/full_screen_image_viewer.dart new file mode 100644 index 00000000..43aee198 --- /dev/null +++ b/lib/views/full_screen_image_viewer.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; + +class FullScreenImageViewer extends StatelessWidget { + final Widget child; + + const FullScreenImageViewer(this.child, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + InteractiveViewer(clipBehavior: Clip.none, panEnabled: true, minScale: 0.5, maxScale: 4.0, child: child).center, + Positioned(top: MediaQuery.of(context).padding.top, left: 12.0, child: const Icon(Icons.clear).toShadowContainer(context, width: 40, padding: 8).onPress(() => Navigator.maybePop(context))), + ], + ), + ); + } +} diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 02c836d1..58e3a971 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/devices_provider.dart'; @@ -15,6 +16,7 @@ import 'package:test_sa/modules/cm_module/views/components/action_button/footer_ 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/full_screen_image_viewer.dart'; import 'package:test_sa/views/widgets/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; @@ -198,18 +200,7 @@ class _AssetImageUploadPageState extends State { Widget imageAngleCard(String icon, String title, GenericAttachmentModel? attachment) { if (attachment != null) { Widget imageWidget = _getImageWidget(attachment.name!); - return ClipRRect( - borderRadius: BorderRadius.circular(12), - child: imageWidget.onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea(child: InteractiveViewer(child: imageWidget).center), - ), - ), - ); - })); + return ClipRRect(borderRadius: BorderRadius.circular(12), child: imageWidget.onPress(() => context.fullScreenImageViewer(imageWidget))); } return Column( mainAxisAlignment: MainAxisAlignment.center, From 04582c9803c6e315574b31bf9615b75b13c6779d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 09:58:53 +0300 Subject: [PATCH 52/73] improvements --- lib/helper/utils.dart | 6 +++--- lib/modules/cx_module/chat/chat_page.dart | 4 ++-- ...{loading_dialog.dart => loading_with_text_dialog.dart} | 8 +++++--- lib/views/widgets/equipment/asset_image_upload_page.dart | 2 +- lib/views/widgets/images/multi_image_picker.dart | 3 ++- 5 files changed, 13 insertions(+), 10 deletions(-) rename lib/views/widgets/dialogs/{loading_dialog.dart => loading_with_text_dialog.dart} (85%) diff --git a/lib/helper/utils.dart b/lib/helper/utils.dart index 0d706f4e..53496f1f 100644 --- a/lib/helper/utils.dart +++ b/lib/helper/utils.dart @@ -13,7 +13,7 @@ import 'package:nfc_manager/nfc_manager_ios.dart' show MiFareIos, Iso15693Ios; 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'; +import 'package:test_sa/views/widgets/dialogs/loading_with_text_dialog.dart'; // ignore_for_file: avoid_annotating_with_dynamic @@ -103,7 +103,7 @@ class Utils { _isLoadingVisible = false; } - static void showUploadingDialog(BuildContext context) { + static void showLoadingWithTextDialog(BuildContext context, {String? message}) { WidgetsBinding.instance.addPostFrameCallback((_) { _isLoadingVisible = true; @@ -111,7 +111,7 @@ class Utils { context: context, barrierColor: Colors.black.withOpacity(0.5), useRootNavigator: false, - builder: (BuildContext context) => const UploadingDialog(), + builder: (BuildContext context) => LoadingWithTextDialog(message: message), ).then((value) { _isLoadingVisible = false; }); diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index 7f92f9ca..301cb1dc 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -391,7 +391,7 @@ class _ChatPageState extends State { clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => ChatFilePicker())) as File?; if (file != null) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1"); Utils.hideLoading(context); } @@ -641,7 +641,7 @@ class _ChatPageState extends State { hoverColor: Colors.transparent, onPressed: () async { if (chatState == ChatState.voiceRecordingCompleted) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); try { await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1"); Utils.hideLoading(context); diff --git a/lib/views/widgets/dialogs/loading_dialog.dart b/lib/views/widgets/dialogs/loading_with_text_dialog.dart similarity index 85% rename from lib/views/widgets/dialogs/loading_dialog.dart rename to lib/views/widgets/dialogs/loading_with_text_dialog.dart index a8a6880e..b07b3eb6 100644 --- a/lib/views/widgets/dialogs/loading_dialog.dart +++ b/lib/views/widgets/dialogs/loading_with_text_dialog.dart @@ -3,8 +3,10 @@ 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); +class LoadingWithTextDialog extends StatelessWidget { + final String? message; + + const LoadingWithTextDialog({Key? key, this.message}) : super(key: key); @override Widget build(BuildContext context) { @@ -27,7 +29,7 @@ class UploadingDialog extends StatelessWidget { const CircularProgressIndicator(strokeWidth: 3), 16.height, Text( - "Uploading...", + message ?? "Uploading...", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), ) ], diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 58e3a971..7cf3aaa1 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -137,7 +137,7 @@ class _AssetImageUploadPageState extends State { ), ); if (result) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); Utils.hideLoading(context); if (result) { diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index e43c0ec3..f31f184e 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -9,6 +9,7 @@ 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/helper/utils.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -665,7 +666,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70); + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 50); if (pickedFile != null) { File fileImage = File(pickedFile.path); From a8667f31c9af1fad835caed846f398f664d1ea0d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 10:36:51 +0300 Subject: [PATCH 53/73] improvements --- .../widgets/equipment/asset_detail_page.dart | 324 ++++++++++-------- 1 file changed, 180 insertions(+), 144 deletions(-) diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 14d09949..d81f8eff 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -39,6 +39,7 @@ class _AssetDetailPageState extends State with SingleTickerProv late AnimationController _animationController; int? assetId; AssetProvider? _assetProvider; + AssetByIdModel? assetModel; @override void initState() { @@ -55,10 +56,6 @@ class _AssetDetailPageState extends State with SingleTickerProv super.dispose(); } - int? assetId; - AssetProvider? _assetProvider; - AssetByIdModel? assetModel; - List attachments = []; @override @@ -85,12 +82,12 @@ class _AssetDetailPageState extends State with SingleTickerProv if (snapshot.hasData) { AssetByIdModel assetModel = snapshot.data!; attachments = assetModel?.assetAttachments - ?.map((attachment) => GenericAttachmentModel( - id: attachment.id, - name: attachment.attachmentName, - originalName: attachment.originalName, - )) - .toList() ?? + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? []; // Start animation when data is loaded _animationController.forward(from: 0.0); @@ -200,160 +197,199 @@ class _AssetDetailPageState extends State with SingleTickerProv ), ); - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Animated image - FadeTransition( - opacity: imageAnimation, - child: ScaleTransition( - scale: imageScaleAnimation, - child: AspectRatio( - aspectRatio: 159 / 94, - child: Container( - width: 95, - height: 95, - decoration: ShapeDecoration( - color: AppColor.neutral30, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), - )), - ), - ), - ), - ), - 6.height, - // Animated content column - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + final attachmentAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0.80, 1.0, curve: Curves.easeOut), + ), + ); + + final attachmentSlideAnimation = Tween( + begin: const Offset(0.0, 0.1), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0.80, 1.0, curve: Curves.easeOutCubic), + ), + ); + + return Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status label with animation - if (assetModel.commissioningStatus != null) - FadeTransition( - opacity: statusAnimation, - child: SlideTransition( - position: statusSlideAnimation, - child: StatusLabel( - label: assetModel.commissioningStatus!.name, - textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + // Animated image + FadeTransition( + opacity: imageAnimation, + child: ScaleTransition( + scale: imageScaleAnimation, + child: AspectRatio( + aspectRatio: 159 / 94, + child: Container( + width: 95, + height: 95, + decoration: ShapeDecoration( + color: AppColor.neutral30, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), + )), ), ), ), - if (assetModel.commissioningStatus != null) 8.height, - - // Header with animation - FadeTransition( - opacity: headerAnimation, - child: SlideTransition( - position: headerSlideAnimation, - child: InfoHeader16Widget(assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), - ), ), - 8.height, - - if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) ...[ - Text( - '*Upload Images for this asset', - style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), - ), - 4.height, - ], + 6.height, + // Animated content column + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status label with animation + if (assetModel.commissioningStatus != null) + FadeTransition( + opacity: statusAnimation, + child: SlideTransition( + position: statusSlideAnimation, + child: StatusLabel( + label: assetModel.commissioningStatus!.name, + textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + ), + ), + ), + if (assetModel.commissioningStatus != null) 8.height, - // Details row with animation - FadeTransition( - opacity: detailsAnimation, - child: SlideTransition( - position: detailsSlideAnimation, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + // Header with animation + FadeTransition( + opacity: headerAnimation, + child: SlideTransition( + position: headerSlideAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2, children: [ - InfoTextWidget(label: context.translation.assetNo, value: assetModel.multiAssets!.first.assetNumber, showEmptyValue: true), - InfoTextWidget(label: context.translation.modelName, value: assetModel.modelDefinition!.modelName, showEmptyValue: true), - InfoTextWidget(label: context.translation.supplier, value: assetModel.supplier?.suppliername, showEmptyValue: true), - InfoTextWidget(label: context.translation.manufacture, value: assetModel.modelDefinition!.manufacturerName, showEmptyValue: true), + InfoHeader16Widget(assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), + if (context.userProvider.isEngineer && !(assetModel.hasTrainingImageData ?? true)) + Text( + '*Upload Images for this asset', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), + ), ], - ).expanded, - 8.width, - Column( + ), + ), + ), + 8.height, + + // Details row with animation + FadeTransition( + opacity: detailsAnimation, + child: SlideTransition( + position: detailsSlideAnimation, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.snNo, value: assetModel.multiAssets!.first.assetSerialNo, showEmptyValue: true), - InfoTextWidget(label: context.translation.site, value: assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.building, value: assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.floor, value: assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.md, value: assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.room, value: assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.assetNo, value: assetModel.multiAssets!.first.assetNumber, showEmptyValue: true), + InfoTextWidget(label: context.translation.modelName, value: assetModel.modelDefinition!.modelName, showEmptyValue: true), + InfoTextWidget(label: context.translation.supplier, value: assetModel.supplier?.suppliername, showEmptyValue: true), + InfoTextWidget(label: context.translation.manufacture, value: assetModel.modelDefinition!.manufacturerName, showEmptyValue: true), + ], + ).expanded, + 8.width, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.snNo, value: assetModel.multiAssets!.first.assetSerialNo, showEmptyValue: true), + InfoTextWidget(label: context.translation.site, value: assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.building, value: assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.floor, value: assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.md, value: assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.room, value: assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + ], + ).expanded, ], - ).expanded, - ], + ), + ), ), - ), - ), - 8.height, + 8.height, - // Divider with animation - FadeTransition( - opacity: datesAnimation, - child: const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - ), - 8.height, - - // Dates section with animation - FadeTransition( - opacity: datesAnimation, - child: SlideTransition( - position: datesSlideAnimation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InfoTextWidget(label: context.translation.installationDate, value: assetModel.installationDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.nextPmDate, value: assetModel.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.lastPmDate, value: assetModel.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), - ], + // Divider with animation + FadeTransition( + opacity: datesAnimation, + child: const Divider(color: AppColor.neutral30, height: 1, thickness: 1), ), - ), - ), + 8.height, - // Description with animation (if exists) - if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ - 8.height, - FadeTransition( - opacity: descAnimation, - child: const Divider().defaultStyle(context), - ), - FadeTransition( - opacity: descAnimation, - child: SlideTransition( - position: descSlideAnimation, - child: InfoTextWidget(label: assetModel.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), + // Dates section with animation + FadeTransition( + opacity: datesAnimation, + child: SlideTransition( + position: datesSlideAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.installationDate, value: assetModel.installationDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.nextPmDate, value: assetModel.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.lastPmDate, value: assetModel.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), + ], + ), + ), ), - ), - ] + + // Description with animation (if exists) + if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ + 8.height, + FadeTransition( + opacity: descAnimation, + child: const Divider().defaultStyle(context), + ), + FadeTransition( + opacity: descAnimation, + child: SlideTransition( + position: descSlideAnimation, + child: InfoTextWidget(label: assetModel.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), + ), + ), + ], + + if (attachments.isNotEmpty) ...[ + 8.height, + FadeTransition( + opacity: attachmentAnimation, + child: const Divider().defaultStyle(context), + ), + FadeTransition( + opacity: attachmentAnimation, + child: SlideTransition( + position: attachmentSlideAnimation, + child: FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()), + ), + ), + ] + ], + ), ], + ).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer && !(assetModel.hasTrainingImageData ?? true)) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: _upload, + ), ), - FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()), - ], - ).toShadowContainer(context), - if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - buttonColor: AppColor.primary10, - label: "Upload Images", - onPressed: _upload, - ), - ), + ], ); } return const Center(child: ALoading()); From 072f3da97e7bc946a52c82f2d9da1b39363063d3 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 12:08:59 +0300 Subject: [PATCH 54/73] improvements --- .../pages/one_time_task_preview_screen.dart | 22 +- .../widgets/cleaner_checklist_widget.dart | 19 +- .../widgets/multi_image_picker.dart | 29 +- .../views/components/history_log_view.dart | 15 +- .../service_request_detail_view.dart | 5 +- .../chat/helper/chat_file_viewer.dart | 12 +- .../chat/view_all_attachment_page.dart | 22 +- .../widgets/cleaner_checklist_widget.dart | 48 +- .../widgets/equipment/asset_detail_page.dart | 71 ++- .../equipment/asset_image_upload_page.dart | 21 +- .../widgets/equipment/my_assets_page.dart | 16 +- lib/views/widgets/images/files_list.dart | 86 +-- .../widgets/images/multi_image_picker.dart | 522 ++++-------------- .../images/multi_image_picker_item.dart | 70 +-- .../widgets/item_views/info_text_widget.dart | 5 +- pubspec.lock | 8 + 16 files changed, 212 insertions(+), 759 deletions(-) diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart index 0e44bcda..6448aeeb 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -272,23 +272,7 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { children: taskData.imageRooms!.map((imageString) { return GestureDetector( onTap: () { - // Show full screen image - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - backgroundColor: Colors.black, - iconTheme: const IconThemeData(color: Colors.white), - ), - body: Center( - child: InteractiveViewer( - child: _buildImage(imageString), - ), - ), - ), - ), - ); + context.fullScreenImageViewer(_buildImage(imageString)); }, child: Container( width: 100, @@ -349,12 +333,12 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { SizedBox( width: 100, child: InfoTextLabelWidget( - label:'$label:', + label: '$label:', ), ), Expanded( child: InfoTextLabelWidget( - label: value, + label: value, ), ), ], diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 3f69efa1..1525ad64 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -277,24 +277,7 @@ class _CleanerChecklistWidgetState extends State { maxImages: 2, // Enforce max 2 images onImageTap: (index, imageUrl) { - // Handle image tap - show in full screen - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer( - child: _buildImageForViewing(imageUrl), - ).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(_buildImageForViewing(imageUrl)); }, onDelete: (index) { // Handle image delete diff --git a/lib/modules/cleaner_module/widgets/multi_image_picker.dart b/lib/modules/cleaner_module/widgets/multi_image_picker.dart index 002acbe4..d970c2d3 100644 --- a/lib/modules/cleaner_module/widgets/multi_image_picker.dart +++ b/lib/modules/cleaner_module/widgets/multi_image_picker.dart @@ -48,8 +48,7 @@ class MultiImagePicker extends StatelessWidget { ), // Add image button (only show if under max limit) - if (enabled && imageUrls.length < maxImages) - _buildAddImageButton(context), + if (enabled && imageUrls.length < maxImages) _buildAddImageButton(context), ], ), ], @@ -77,7 +76,9 @@ class MultiImagePicker extends StatelessWidget { if (onImageTap != null) { onImageTap!(index, imageUrl); } else { - _showFullImage(context, imageUrl); + context.fullScreenImageViewer( + _getImageWidget(imageUrl), + ); } }), if (enabled) @@ -193,26 +194,4 @@ class MultiImagePicker extends StatelessWidget { } } } - - void _showFullImage(BuildContext context, String imageUrl) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Stack( - children: [ - Center( - child: InteractiveViewer( - child: _getImageWidget(imageUrl), - ), - ), - const ABackButton(), - ], - ), - ), - ), - ), - ); - } } diff --git a/lib/modules/cm_module/views/components/history_log_view.dart b/lib/modules/cm_module/views/components/history_log_view.dart index e549f02f..7b7b1b5f 100644 --- a/lib/modules/cm_module/views/components/history_log_view.dart +++ b/lib/modules/cm_module/views/components/history_log_view.dart @@ -120,20 +120,7 @@ class HistoryLogView extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 12.toScreenWidth), child: InteractiveViewer(child: Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!, height: 143.toScreenHeight)), ).onPress(() async { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer(child: Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!)); }), ], title.bodyText(context).custom(color: AppColor.textColor(context)), 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 bd9ecbe6..4d8cc3d0 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 @@ -205,10 +205,7 @@ class _ServiceRequestDetailViewState extends State { ], ), if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[ - Text( - '*Images are required for the asset', - style: AppTextStyles.bodyText.copyWith(color: AppColor.red30), - ), + InfoTextLabelWidget(label: '*Images are required for the asset', color: AppColor.red30), 4.height, ], InfoTextWidget(label: context.translation.assetName, value: workOrder.assetNdModel!.name?.cleanupWhitespace.capitalizeFirstOfEach), diff --git a/lib/modules/cx_module/chat/helper/chat_file_viewer.dart b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart index 9eee8c44..34653e1b 100644 --- a/lib/modules/cx_module/chat/helper/chat_file_viewer.dart +++ b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart @@ -24,7 +24,6 @@ class ChatFileViewer extends StatelessWidget { @override Widget build(BuildContext context) { - return FutureBuilder( future: checkFileInLocalStorage(), builder: (BuildContext context, AsyncSnapshot snapshot) { @@ -80,16 +79,7 @@ class ChatFileViewer extends StatelessWidget { 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, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(file)); } else { OpenFile.open(file.path); } diff --git a/lib/modules/cx_module/chat/view_all_attachment_page.dart b/lib/modules/cx_module/chat/view_all_attachment_page.dart index 1e442591..8eb13b7d 100644 --- a/lib/modules/cx_module/chat/view_all_attachment_page.dart +++ b/lib/modules/cx_module/chat/view_all_attachment_page.dart @@ -182,16 +182,7 @@ class AttachmentFileViewer extends StatelessWidget { ), ).onPress(() { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer(child: Image.file(snapshot.data!)).center, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(snapshot.data!)); } else { OpenFile.open(snapshot.data!.path); } @@ -253,16 +244,7 @@ class AttachmentFileViewer extends StatelessWidget { 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, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(file)); } else { OpenFile.open(file.path); } diff --git a/lib/modules/export_module/widgets/cleaner_checklist_widget.dart b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart index 12d22165..090de70b 100644 --- a/lib/modules/export_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart @@ -200,21 +200,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], @@ -241,21 +227,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], @@ -300,21 +272,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index d81f8eff..a876ffeb 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -80,7 +80,7 @@ class _AssetDetailPageState extends State with SingleTickerProv return const Center(child: ALoading()); } else if (snapshot.hasData) {} if (snapshot.hasData) { - AssetByIdModel assetModel = snapshot.data!; + assetModel = snapshot.data!; attachments = assetModel?.assetAttachments ?.map((attachment) => GenericAttachmentModel( id: attachment.id, @@ -89,8 +89,10 @@ class _AssetDetailPageState extends State with SingleTickerProv )) .toList() ?? []; - // Start animation when data is loaded - _animationController.forward(from: 0.0); + + if (!_animationController.isCompleted) { + _animationController.forward(from: 0.0); + } // Image animation - fade in with scale final imageAnimation = Tween(begin: 0.0, end: 1.0).animate( @@ -238,7 +240,7 @@ class _AssetDetailPageState extends State with SingleTickerProv ), image: DecorationImage( fit: BoxFit.cover, - image: NetworkImage(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), + image: NetworkImage(assetModel?.assetPhoto != null ? URLs.getFileUrl(assetModel!.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), )), ), ), @@ -251,19 +253,19 @@ class _AssetDetailPageState extends State with SingleTickerProv crossAxisAlignment: CrossAxisAlignment.start, children: [ // Status label with animation - if (assetModel.commissioningStatus != null) + if (assetModel?.commissioningStatus != null) FadeTransition( opacity: statusAnimation, child: SlideTransition( position: statusSlideAnimation, child: StatusLabel( - label: assetModel.commissioningStatus!.name, - textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + label: assetModel!.commissioningStatus!.name, + textColor: AppColor.getRequestStatusTextColorByName(context, assetModel!.commissioningStatus!.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel!.commissioningStatus!.name!), ), ), ), - if (assetModel.commissioningStatus != null) 8.height, + if (assetModel?.commissioningStatus != null) 8.height, // Header with animation FadeTransition( @@ -275,8 +277,8 @@ class _AssetDetailPageState extends State with SingleTickerProv crossAxisAlignment: CrossAxisAlignment.start, spacing: 2, children: [ - InfoHeader16Widget(assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), - if (context.userProvider.isEngineer && !(assetModel.hasTrainingImageData ?? true)) + InfoHeader16Widget(assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), + if (context.userProvider.isEngineer && !(assetModel?.hasTrainingImageData ?? true)) Text( '*Upload Images for this asset', style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), @@ -286,7 +288,6 @@ class _AssetDetailPageState extends State with SingleTickerProv ), ), 8.height, - // Details row with animation FadeTransition( opacity: detailsAnimation, @@ -294,41 +295,39 @@ class _AssetDetailPageState extends State with SingleTickerProv position: detailsSlideAnimation, child: Row( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.assetNo, value: assetModel.multiAssets!.first.assetNumber, showEmptyValue: true), - InfoTextWidget(label: context.translation.modelName, value: assetModel.modelDefinition!.modelName, showEmptyValue: true), - InfoTextWidget(label: context.translation.supplier, value: assetModel.supplier?.suppliername, showEmptyValue: true), - InfoTextWidget(label: context.translation.manufacture, value: assetModel.modelDefinition!.manufacturerName, showEmptyValue: true), + InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true), + InfoTextWidget(label: context.translation.modelName, value: assetModel!.modelDefinition!.modelName, showEmptyValue: true), + InfoTextWidget(label: context.translation.supplier, value: assetModel!.supplier?.suppliername, showEmptyValue: true), + InfoTextWidget(label: context.translation.manufacture, value: assetModel!.modelDefinition!.manufacturerName, showEmptyValue: true), ], ).expanded, - 8.width, Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.snNo, value: assetModel.multiAssets!.first.assetSerialNo, showEmptyValue: true), - InfoTextWidget(label: context.translation.site, value: assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.building, value: assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.floor, value: assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.md, value: assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.room, value: assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.snNo, value: assetModel!.multiAssets!.first.assetSerialNo, showEmptyValue: true), + InfoTextWidget(label: context.translation.site, value: assetModel!.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.building, value: assetModel!.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.floor, value: assetModel!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.md, value: assetModel!.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.room, value: assetModel!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), ], ).expanded, ], ), ), ), - 8.height, - + // 8.height, // Divider with animation FadeTransition( opacity: datesAnimation, - child: const Divider(color: AppColor.neutral30, height: 1, thickness: 1), + child: const Divider().defaultStyle(context), ), - 8.height, - + // 8.height, // Dates section with animation FadeTransition( opacity: datesAnimation, @@ -337,17 +336,17 @@ class _AssetDetailPageState extends State with SingleTickerProv child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.installationDate, value: assetModel.installationDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.nextPmDate, value: assetModel.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.lastPmDate, value: assetModel.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.installationDate, value: assetModel?.installationDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.nextPmDate, value: assetModel?.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.lastPmDate, value: assetModel?.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), ], ), ), ), // Description with animation (if exists) - if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ - 8.height, + if ((assetModel?.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ + // 8.height, FadeTransition( opacity: descAnimation, child: const Divider().defaultStyle(context), @@ -356,13 +355,13 @@ class _AssetDetailPageState extends State with SingleTickerProv opacity: descAnimation, child: SlideTransition( position: descSlideAnimation, - child: InfoTextWidget(label: assetModel.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), + child: InfoTextWidget(label: assetModel!.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), ), ), ], if (attachments.isNotEmpty) ...[ - 8.height, + // 8.height, FadeTransition( opacity: attachmentAnimation, child: const Divider().defaultStyle(context), @@ -380,7 +379,7 @@ class _AssetDetailPageState extends State with SingleTickerProv ], ).toShadowContainer(context), ).expanded, - if (context.userProvider.isEngineer && !(assetModel.hasTrainingImageData ?? true)) + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 7cf3aaa1..33d48a62 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -19,6 +19,8 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/full_screen_image_viewer.dart'; import 'package:test_sa/views/widgets/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; class AssetImageUploadPage extends StatefulWidget { int assetId; @@ -55,14 +57,10 @@ class _AssetImageUploadPageState extends State { ListView( padding: const EdgeInsets.all(16), children: [ - Text( - "Instructions", - style: AppTextStyles.heading5.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), - Text( - '* Please upload all images of the sides of the asset as mentioned below.\n* Images should be clear and high quality.\n* Avoid uploading blurry, low-light or low-resolution images.', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), + InfoHeader16Widget("Instructions"), + InfoTextLabelWidget( + label: + "* Please upload all images of the sides of the asset as mentioned below.\n* Images should be clear and high quality.\n* Avoid uploading blurry, low-light or low-resolution images."), GridView( padding: const EdgeInsets.only(top: 16, bottom: 16), shrinkWrap: true, @@ -110,11 +108,13 @@ class _AssetImageUploadPageState extends State { ), AttachmentPicker( label: "Additional Images", + pickerTitle: "Attach Images", attachment: trainingImageData.additionalImages!, buttonColor: AppColor.black10, onlyImages: true, pickMultiple: true, - buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + // showAsGrid: true, + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), onChange: (_attachments) { trainingImageData.additionalImages = _attachments; setState(() {}); @@ -163,11 +163,12 @@ class _AssetImageUploadPageState extends State { Positioned.fill( child: AttachmentPicker( label: "", + pickerTitle: title, onlyImages: true, checkQuality: true, enabled: attachment == null, attachment: attachment == null ? [] : [attachment], - buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), onChange: (_attachments) { if (_attachments.isNotEmpty) { onUpdate(_attachments.first); diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index 92a168a8..43fbbd6d 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -129,13 +129,20 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt _searchableList.clear(); _searchableList.addAll(_devicesProvider.devices); }).expanded, - 16.width, - Icon(Icons.grid_view_outlined, color: AppColor.icon2Color(context)).onPress(() => setState(() { - showListView = !showListView; - })), + 12.width, + IconButton( + icon: Icon(!showListView ? Icons.view_list_sharp : Icons.grid_view_outlined, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: !showListView ? "View as List" : "View as Grid", + onPressed: () => setState(() { + showListView = !showListView; + }), + ), IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: () async { await Navigator.push( @@ -154,6 +161,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt IconButton( icon: "filter".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => DeviceFilterScreen(data: _searchAsset))); diff --git a/lib/views/widgets/images/files_list.dart b/lib/views/widgets/images/files_list.dart index 851a19f7..9ca23d8a 100644 --- a/lib/views/widgets/images/files_list.dart +++ b/lib/views/widgets/images/files_list.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:open_file/open_file.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'; @@ -84,21 +85,7 @@ class FilesList extends StatelessWidget { recognizer: TapGestureRecognizer() ..onTap = () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: getImageWidget(itemIndex)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(getImageWidget(itemIndex)); } else if (_isLocalUrl(images[itemIndex])) { OpenFile.open(images[itemIndex]); } else { @@ -162,21 +149,7 @@ class FilesList extends StatelessWidget { ), ).onPress(() async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: getImageWidget(itemIndex)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(getImageWidget(itemIndex)); } else if (_isLocalUrl(images[itemIndex])) { OpenFile.open(images[itemIndex]); } else { @@ -192,59 +165,6 @@ class FilesList extends StatelessWidget { }); }, ); - - // return ListView.separated( - // padding: padding ?? EdgeInsets.symmetric(horizontal: 32 * AppStyle.getScaleFactor(context)), - // scrollDirection: Axis.horizontal, - // itemCount: images?.length ?? 0, - // separatorBuilder: (context, itemIndex) => 8.width, - // itemBuilder: (context, itemIndex) { - // if (images[itemIndex] == null || !images[itemIndex].contains(".")) { - // return Text("Bad\nFile"); - // } - // var isImage = - // images[itemIndex].split(".").last.toLowerCase() == "png" || images[itemIndex].split(".").last.toLowerCase() == "jpg" || images[itemIndex].split(".").last.toLowerCase() == "jpeg"; - // var isPdf = images[itemIndex].split(".").last.toLowerCase() == "pdf"; - // var isExcel = images[itemIndex].split(".").last.toLowerCase() == "xlsx"; - // - // return (isImage - // ? (_isLocalUrl(images[itemIndex]) - // ? Image.file(File(images[itemIndex])) - // : ImageLoader( - // url: images[itemIndex], - // boxFit: BoxFit.cover, - // )) - // : Image.asset("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png")) - // .onPress(() async { - // if (isImage) { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (_) => Scaffold( - // body: SafeArea( - // child: Stack( - // children: [ - // InteractiveViewer(child: Image(image: (_isLocalUrl(images[itemIndex]) ? FileImage(File(images[itemIndex])) : NetworkImage(images[itemIndex])))).center, - // const ABackButton(), - // ], - // ), - // ), - // ), - // ), - // ); - // } else if (_isLocalUrl(images[itemIndex])) { - // OpenFile.open(images[itemIndex]); - // } else { - // try { - // if (!await launchUrl(Uri.parse(images[itemIndex]), mode: LaunchMode.externalApplication)) { - // Fluttertoast.showToast(msg: "UnExpected Error with file."); - // throw Exception('Could not launch '); - // } - // } catch (ex) { - // Fluttertoast.showToast(msg: "No Viewer found."); - // } - // } - // }); - // }); } Widget getImageWidget(int itemIndex) { diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index 58165803..a38bc0d5 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -17,280 +17,6 @@ import 'package:test_sa/new_views/app_style/app_color.dart'; import '../../../new_views/common_widgets/app_dashed_button.dart'; import 'multi_image_picker_item.dart'; -// class MultiFilesPicker extends StatefulWidget { -// final String label; -// final bool error; -// final List files; -// final List attachment; -// -// final bool enabled, onlyImages; -// double? buttonHeight; -// Widget? buttonIcon; -// Color? buttonColor; -// final Function(List)? onChange; -// final bool showAsGrid; -// -// MultiFilesPicker( -// {Key? key, -// this.files = const [], -// 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() => _MultiFilesPickerState(); -// } -// -// class _MultiFilesPickerState extends State { -// @override -// Widget build(BuildContext context) { -// return Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// if (widget.enabled) ...[ -// 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.files.isNotEmpty) -// Wrap( -// spacing: 8.toScreenWidth, -// children: List.generate( -// widget.files!.length, -// (index) { -// File image = widget.files![index]; -// return MultiFilesPickerItem( -// file: image, -// enabled: widget.enabled, -// onRemoveTap: (image) { -// if (!widget.enabled) { -// return; -// } -// widget.files.remove(image); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// 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.files.add(File(path!)); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// } -// setState(() {}); -// } -// } -// -// void showFileSourceSheet() async { -// if (widget.files.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?; -// 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.files.add(fileImage); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// 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 { -// if (widget.files.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 GestureDetector( -// onTap: onTap, -// child: Container( -// constraints: BoxConstraints(minWidth: 111.toScreenWidth, minHeight: 111.toScreenHeight), -// padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 12.toScreenHeight), -// decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(width: 1, color: AppColor.white70)), -// child: Column( -// mainAxisSize: MainAxisSize.min, -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// icon.toSvgAsset(), -// 24.height, -// label.bodyText2(context).custom(color: AppColor.black20), -// ], -// ), -// ), -// ); -// } -// -// return 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); -// }, -// ), -// ], -// ), -// ); -// }, -// ); -// // 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); -// if (fileImage != null) { -// widget.files.add(fileImage); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// setState(() {}); -// } -// } -// -// setState(() {}); -// } -// } - class AttachmentModel { int id = 0; File? file; @@ -313,6 +39,7 @@ class AttachmentModel { } class AttachmentPicker extends StatefulWidget { + final String pickerTitle; final String label; final bool error; final List attachment; @@ -322,7 +49,6 @@ class AttachmentPicker extends StatefulWidget { Widget? buttonIcon; Color? buttonColor; final Function(List)? onChange; - final bool showAsGrid; final bool showAsListView; final bool pickMultiple; Lookup? documentType; @@ -333,6 +59,7 @@ class AttachmentPicker extends StatefulWidget { {Key? key, this.attachment = const [], required this.label, + this.pickerTitle = "Attach File", this.error = false, this.buttonHeight, this.buttonIcon, @@ -340,7 +67,6 @@ class AttachmentPicker extends StatefulWidget { this.onlyImages = false, this.onChange, this.documentType, - this.showAsGrid = false, this.showAsListView = false, this.pickMultiple = false, this.checkQuality = false, @@ -352,15 +78,29 @@ class AttachmentPicker extends StatefulWidget { State createState() => _AttachmentPickerState(); } -class _AttachmentPickerState extends State { +class _AttachmentPickerState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return widget.child != null - ? widget.child!.onPress((widget.enabled == false) - ? () {} - : widget.showAsGrid - ? () => showFileSourceSheet() - : () => onFilePicker(widget.checkQuality)) + ? widget.child!.onPress((widget.enabled == false) ? () {} : () => onFilePicker(widget.checkQuality)) : Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 12, @@ -370,11 +110,7 @@ class _AttachmentPickerState extends State { height: widget.buttonHeight, buttonColor: widget.buttonColor, icon: widget.buttonIcon, - onPressed: (widget.enabled == false) - ? () {} - : widget.showAsGrid - ? () => showFileSourceSheet() - : () => onFilePicker(widget.checkQuality)), + onPressed: (widget.enabled == false) ? () {} : () => onFilePicker(widget.checkQuality)), if (widget.attachment.isNotEmpty) widget.showAsListView ? ListView.separated( @@ -446,57 +182,6 @@ class _AttachmentPickerState extends State { } } - void showFileSourceSheet() async { - 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; - - // if(widget.pickMultiple) { - final pickedFile12 = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); - - // // } - // - // 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, documentTypeId: widget.documentType)); - // if (widget.onChange != null) { - // widget.onChange!(widget.attachment); - // } - // setState(() {}); - // } - } - Widget gridItem(IconData iconData, String title) { return Container( padding: const EdgeInsets.all(12), @@ -509,7 +194,7 @@ class _AttachmentPickerState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Icon(iconData, color: AppColor.icon2Color(context), size: 24), + Icon(iconData, color: AppColor.icon2Color(context), size: 24), title.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), // Text( // title, @@ -521,6 +206,10 @@ class _AttachmentPickerState extends State { } onFilePicker(bool checkQuality) async { + // Reset and restart animation for bottom sheet + _animationController.reset(); + _animationController.forward(); + ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -536,116 +225,95 @@ class _AttachmentPickerState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - icon.toSvgAsset(color: AppColor.icon2Color(context), width: 36, height: 36), + icon.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), label.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), ], ), ).onPress(onTap); } + List listItems = [ + 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); + }, + ), + ]; + return SafeArea( top: false, child: Container( width: double.infinity, - color: AppColor.background(context), + color: AppColor.bottomSheetColor(context), + padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Attach File".heading4(context), - 12.height, - GridView( - padding: const EdgeInsets.all(0), + widget.pickerTitle.heading4(context), + GridView.builder( + padding: const EdgeInsets.only(top: 16, bottom: 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); - }, - ), - ], + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16), + itemCount: listItems.length, + itemBuilder: (context, index) { + // Calculate staggered animation delay + final delay = index * 0.08; + final animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOut, + ), + ), + ); + + final slideAnimation = Tween( + begin: const Offset(0.0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOutCubic, + ), + ), + ); + + return FadeTransition( + opacity: animation, + child: SlideTransition(position: slideAnimation, child: listItems[index]), + ); + }, ), - // 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; if (widget.pickMultiple && source == ImageSource.gallery) { diff --git a/lib/views/widgets/images/multi_image_picker_item.dart b/lib/views/widgets/images/multi_image_picker_item.dart index acd8a992..e555a9dd 100644 --- a/lib/views/widgets/images/multi_image_picker_item.dart +++ b/lib/views/widgets/images/multi_image_picker_item.dart @@ -70,15 +70,20 @@ class MultiFilesPickerItem extends StatelessWidget { ), 8.width, if (enabled) - InkWell( - child: "remove".toSvgAsset(width: 16,color: AppColor.red30), - onTap: () { + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { onRemoveTap(file); if (onRemove != null) { onRemove!(); } }, - ) + ), ], ), 4.height, @@ -92,23 +97,12 @@ class MultiFilesPickerItem extends StatelessWidget { recognizer: TapGestureRecognizer() ..onTap = () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer( - child: _isLocalUrl(file.path) - ? Image.file(file) - : ImageLoader( - url: URLs.getFileUrl(file.path), - boxFit: BoxFit.cover, - ), - ).center, - ), - ), - ), - ); + context.fullScreenImageViewer(_isLocalUrl(file.path) + ? Image.file(file) + : ImageLoader( + url: URLs.getFileUrl(file.path), + boxFit: BoxFit.cover, + )); } else if (_isLocalUrl(file.path)) { OpenFile.open(file.path); } else { @@ -150,23 +144,12 @@ class MultiFilesPickerItem extends StatelessWidget { padding: EdgeInsets.zero, onPressed: () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer( - child: _isLocalUrl(file.path) - ? Image.file(file) - : ImageLoader( - url: URLs.getFileUrl(file.path), - boxFit: BoxFit.cover, - ), - ).center, - ), - ), - ), - ); + context.fullScreenImageViewer(_isLocalUrl(file.path) + ? Image.file(file) + : ImageLoader( + url: URLs.getFileUrl(file.path), + boxFit: BoxFit.cover, + )); } else if (_isLocalUrl(file.path)) { OpenFile.open(file.path); } else { @@ -179,9 +162,14 @@ class MultiFilesPickerItem extends StatelessWidget { ), ), if (enabled) - InkWell( - child: "remove".toSvgAsset(width: 16), - onTap: () { + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { onRemoveTap(file); if (onRemove != null) { onRemove!(); diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index 661e1ab5..b3a2b252 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -51,13 +51,14 @@ class InfoTextWidget extends StatelessWidget { class InfoTextLabelWidget extends StatelessWidget { final String label; + final Color? color; - InfoTextLabelWidget({required this.label, Key? key}) : super(key: key); + InfoTextLabelWidget({required this.label, this.color, Key? key}) : super(key: key); @override Widget build(BuildContext context) { Widget text; - text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))); + text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: color ?? AppColor.lightTextColor(context))); return text; } } diff --git a/pubspec.lock b/pubspec.lock index 937cec84..8fa3ed1c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -758,6 +758,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.0" + image_blur_detection: + dependency: "direct main" + description: + name: image_blur_detection + sha256: "0d1043422b90b97dea2aa282aa5cbb206e34bdc43acc0cb312b31f160f977333" + url: "https://pub.dev" + source: hosted + version: "1.0.1" image_cropper: dependency: "direct main" description: From deeff9e13ee1c957c6e01885d885652d882371b2 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 15:38:09 +0300 Subject: [PATCH 55/73] ui ux improvements --- .../widgets/app_bar_widget.dart | 40 +- .../widgets/request_category_fragment.dart | 86 +- .../pages/asset_inventory_detail_view.dart | 4 +- .../pages/one_time_task_preview_screen.dart | 6 +- .../pages/one_time_task_view.dart | 6 +- .../widgets/cleaner_checklist_widget.dart | 16 +- .../components/activities_list_view.dart | 4 +- .../views/components/history_log_view.dart | 2 +- .../views/components/initial_visit_card.dart | 2 +- .../service_request_detail_view.dart | 12 +- .../create_incident_request_page.dart | 2 +- .../incident_module/incident_detail_page.dart | 64 +- .../pages/loan_equipment_detail_page.dart | 2 +- .../medical_gas_inspection_detail_page.dart | 2 +- .../land_page/mark_attendance_widget.dart | 866 ------------------ .../my_request/my_requests_page.dart | 6 +- .../non_hmg_employee_swipe_view.dart | 2 + lib/new_views/pages/login_page.dart | 10 +- .../swipe_module/mark_attendance_widget.dart | 821 ----------------- .../swipe_module/swipe_history_view.dart | 159 ++-- .../utils/swipe_general_utils.dart | 21 +- lib/views/full_screen_image_viewer.dart | 20 +- .../auto_complete/wo_auto_complete_field.dart | 15 +- .../widgets/equipment/asset_detail_page.dart | 5 +- .../widgets/equipment/my_assets_page.dart | 5 +- .../widgets/item_views/info_text_widget.dart | 27 +- lib/views/widgets/qr/scan_qr.dart | 10 +- 27 files changed, 276 insertions(+), 1939 deletions(-) delete mode 100644 lib/new_views/pages/land_page/mark_attendance_widget.dart delete mode 100644 lib/new_views/swipe_module/mark_attendance_widget.dart diff --git a/lib/dashboard_latest/widgets/app_bar_widget.dart b/lib/dashboard_latest/widgets/app_bar_widget.dart index c2865647..d6ac1c50 100644 --- a/lib/dashboard_latest/widgets/app_bar_widget.dart +++ b/lib/dashboard_latest/widgets/app_bar_widget.dart @@ -211,7 +211,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix [], builder: (_, MenuController controller, Widget? child) { return Container( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: AppColor.assetGroupBackground(context), @@ -222,10 +222,13 @@ class _AppBarWidgetState extends State with TickerProviderStateMix ), child: Row( mainAxisSize: MainAxisSize.min, - spacing: 16, + spacing: 8, children: [ - Text(settingProvider.assetGroup?.name ?? "", style: AppTextStyles.bodyText2.copyWith(color: AppColor.headingTextColor(context))), - Icon(Icons.keyboard_arrow_down, color: AppColor.headingTextColor(context)), + Text( + settingProvider.assetGroup?.name ?? "", + style: AppTextStyles.bodyText2.copyWith(color: AppColor.headingTextColor(context)), + ), + Icon(Icons.keyboard_arrow_down, color: AppColor.headingTextColor(context), size: 20), ], ), ).onPress(() { @@ -240,7 +243,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix }), ), ), - 16.width, + 8.width, SlideTransition( position: _rightSlideAnimation, child: FadeTransition( @@ -248,20 +251,31 @@ class _AppBarWidgetState extends State with TickerProviderStateMix child: Row( children: [ if (context.settingProvider.isUserFlowMedical) ...[ - ("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() { - Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList())); - }), - 16.width, + IconButton( + icon: ("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "Notifications", + onPressed: () { + Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList())); + }, + ), ], - ("dashboard/notification").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() { - Navigator.of(context).pushNamed(NotificationsPage.id); - }), + IconButton( + icon: ("dashboard/notification").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "Notifications", + onPressed: () { + Navigator.of(context).pushNamed(NotificationsPage.id); + }, + ), ], ), ), ), ], - ).paddingOnly(start: 16, end: 16); + ).paddingOnly(start: 16, end: 08); }), ); } diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index 24598a4b..c6f80cad 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -117,8 +117,6 @@ class _RequestCategoryFragmentState extends State with final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - - // !isNurse && dashboardProvider.tabs.isNotEmpty && dashboardProvider.tabs[dashboardProvider.currentListIndex].tag == 0 // ? Column( // crossAxisAlignment: CrossAxisAlignment.start, @@ -193,46 +191,50 @@ class _RequestCategoryFragmentState extends State with // ).paddingOnly(start: 14, end: 12) // : - - isHouseKeeper - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 'All Tasks'.addTranslation.heading4(context), - 'add_icon'.toSvgAsset(width: 24, height: 24,).onPress(() { - Navigator.of(context).pushNamed('/cleaner_search'); - }), - ], - ).paddingOnly(start: 16, end: 16) : - Row( - spacing: 16, - children: [ - Text(context.translation.allWorkOrder, style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context))).expanded, - // todo commenting this code, for FM - // if (!isFMSEngineer) - // 'search'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { - // Navigator.of(context).pushNamed('/cleaner_search'); - // }), - - if (dashboardProvider.listIndex.isNotEmpty) - Text( - 'Clear Filters', - style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), - ).onPress(() { - dashboardProvider.resetRequestDataList(); - dashboardProvider.currentListIndex = null; - Provider.of(context, listen: false) - ..reset() - ..getAllRequests(context); - }), - Transform.flip( - flipY: isAscending, - child: 'dashboard/sort_descending'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { - _toggleSort(dashboardProvider, user?.type); - }), - ) - ], - ).paddingOnly(start: 16, end: 16), + isHouseKeeper + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'All Tasks'.addTranslation.heading4(context), + 'add_icon' + .toSvgAsset( + width: 24, + height: 24, + ) + .onPress(() { + Navigator.of(context).pushNamed('/cleaner_search'); + }), + ], + ).paddingOnly(start: 16, end: 16) + : Row( + spacing: 16, + children: [ + Text(context.translation.allWorkOrder, style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context))).expanded, + // todo commenting this code, for FM + // if (!isFMSEngineer) + // 'search'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { + // Navigator.of(context).pushNamed('/cleaner_search'); + // }), + + if (dashboardProvider.listIndex.isNotEmpty) + Text( + 'Clear Filters', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), + ).onPress(() { + dashboardProvider.resetRequestDataList(); + dashboardProvider.currentListIndex = null; + Provider.of(context, listen: false) + ..reset() + ..getAllRequests(context); + }), + Transform.flip( + flipY: isAscending, + child: 'dashboard/sort_descending'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { + _toggleSort(dashboardProvider, user?.type); + }), + ) + ], + ).paddingOnly(start: 16, end: 16), ), ), diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart index 1f0b0481..48688b2f 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart @@ -69,7 +69,7 @@ class _AssetInventoryDetailViewState extends State { InfoTextWidget(label: 'Start Date'.addTranslation, value: sessionModel.startDate?.toServiceRequestDetailsFormat ?? '-'), InfoTextWidget(label: 'End Date'.addTranslation, value: sessionModel.endDate?.toServiceRequestDetailsFormat ?? '-'), ], - ).toShadowContainer(context, padding: 16, borderRadius: 20); + ).toShadowContainer(context, borderRadius: 20); } Widget siteListCard(BuildContext context, SessionModel sessionModel) { @@ -84,7 +84,7 @@ class _AssetInventoryDetailViewState extends State { sites: sites, ), ], - ).toShadowContainer(context, padding: 16, borderRadius: 20); + ).toShadowContainer(context, borderRadius: 20); } } diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart index 6448aeeb..19ef67fb 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -262,10 +262,8 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoHeader16Widget( - 'Captured Images', - ), - 16.height, + InfoHeader16Widget('Captured Images'), + 12.height, Wrap( spacing: 8, runSpacing: 8, diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index 5a94f7e6..25ac466d 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -159,7 +159,7 @@ class _OneTimeTaskViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTaskInfoCard(context), - 16.height, + 12.height, CleanerChecklistWidget( isClosed: isClosed, checklistItems: _convertInstructionsToChecklist(), @@ -276,9 +276,9 @@ class _OneTimeTaskViewState extends State { ), ], ], - ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), + ), ], - ).toShadowContainer(context, padding: 0); + ).toShadowContainer(context); } List _convertInstructionsToChecklist() { diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 1525ad64..f6b0a1a0 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -37,18 +37,17 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ InfoHeader16Widget('Checklist'), - 16.height, ListView.separated( shrinkWrap: true, padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), itemBuilder: (cxt, index) => _buildChecklistItem(context, widget.checklistItems[index]), - separatorBuilder: (cxt, index) => const Divider(height: 1, color: AppColor.neutral30, thickness: 1).paddingOnly(top: 16, bottom: 16), + separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), itemCount: widget.checklistItems.length) - .toShadowContainer(context, padding: 16), - 16.height, + .toShadowContainer(context), _buildImageCaptureWidget(context), ], ); @@ -70,7 +69,7 @@ class _CleanerChecklistWidgetState extends State { child: Checkbox( value: item.isChecked, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: BorderSide(color: Colors.black.withValues(alpha: .05)), + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), activeColor: AppColor.primary10, onChanged: (bool? value) {}, @@ -181,12 +180,7 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InfoHeader16Widget('Captured Image'), - ], - ), + InfoHeader16Widget('Captured Image'), 8.height, _buildImageWidget(context), ], diff --git a/lib/modules/cm_module/views/components/activities_list_view.dart b/lib/modules/cm_module/views/components/activities_list_view.dart index d3880a44..a543651f 100644 --- a/lib/modules/cm_module/views/components/activities_list_view.dart +++ b/lib/modules/cm_module/views/components/activities_list_view.dart @@ -175,7 +175,7 @@ class _ActivitiesListViewState extends State { FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()), ], ], - ).toShadowContainer(context, padding: 12, showShadow: false).onPress(() { + ).toShadowContainer(context, showShadow: false).onPress(() { if (showActionButtons) return; if (requestDetailProvider.isReadOnlyRequest) { editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); @@ -272,7 +272,7 @@ class _ActivitiesListViewState extends State { // // ), // ] ], - ).toShadowContainer(context, padding: 12, showShadow: false).onPress(() { + ).toShadowContainer(context, showShadow: false).onPress(() { if (showActionButtons) return; if (requestDetailProvider.isReadOnlyRequest) { editMaintenanceRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); diff --git a/lib/modules/cm_module/views/components/history_log_view.dart b/lib/modules/cm_module/views/components/history_log_view.dart index 7b7b1b5f..b3fef162 100644 --- a/lib/modules/cm_module/views/components/history_log_view.dart +++ b/lib/modules/cm_module/views/components/history_log_view.dart @@ -129,7 +129,7 @@ class HistoryLogView extends StatelessWidget { ), ), ], - ).toShadowContainer(context, padding: 16).paddingOnly(bottom: 16); + ).toShadowContainer(context).paddingOnly(bottom: 16); } Widget statusCard(BuildContext context, String status) { diff --git a/lib/modules/cm_module/views/components/initial_visit_card.dart b/lib/modules/cm_module/views/components/initial_visit_card.dart index 424351a5..ecd93663 100644 --- a/lib/modules/cm_module/views/components/initial_visit_card.dart +++ b/lib/modules/cm_module/views/components/initial_visit_card.dart @@ -55,7 +55,7 @@ class _InitialVisitCardState extends State { requestDetailProvider.needVisitHelperModel!.comment!.bodyText2(context).custom(color: AppColor.neutral120), ] ], - ).toShadowContainer(context, padding: 16); + ).toShadowContainer(context); }); } } 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 4d8cc3d0..73a7f9ea 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 @@ -205,11 +205,11 @@ class _ServiceRequestDetailViewState extends State { ], ), if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[ - InfoTextLabelWidget(label: '*Images are required for the asset', color: AppColor.red30), + InfoTextLabelWidget(label: '*Images are required for the asset', color: AppColor.red30, textStyle: AppTextStyles.tinyFont2), 4.height, ], InfoTextWidget(label: context.translation.assetName, value: workOrder.assetNdModel!.name?.cleanupWhitespace.capitalizeFirstOfEach), - InfoTextWidget(label: context.translation.assetNumber, value: workOrder.asset!.assetNumber), + InfoTextWidget(label: context.translation.assetNumber, value: workOrder.asset!.assetNumber, copyValue: true), InfoTextWidget(label: context.translation.serialNo, value: workOrder.asset!.assetSerialNo), InfoTextWidget(label: context.translation.manufacture, value: workOrder.manufacturer?.name?.cleanupWhitespace.capitalizeFirstOfEach), InfoTextWidget(label: context.translation.model, value: workOrder.model?.name!.cleanupWhitespace.capitalizeFirstOfEach), @@ -227,7 +227,7 @@ class _ServiceRequestDetailViewState extends State { //... request details starts here.... InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.cmNumber, value: workOrder.workOrderNo), + InfoTextWidget(label: context.translation.cmNumber, value: workOrder.workOrderNo, copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: workOrder.workOrderCreatedBy?.userName ?? "-"), InfoTextWidget(label: context.translation.arrivedDate, value: workOrder.iHaveArrivedDate != null ? workOrder.iHaveArrivedDate.toString().toServiceRequestDetailsFormat : '-'), InfoTextWidget(label: context.translation.closedDate, value: workOrder.closedDate != null ? workOrder.closedDate!.toString().toServiceRequestDetailsFormat : '-'), @@ -297,9 +297,7 @@ class _ServiceRequestDetailViewState extends State { assetRetiredButton(context: context), ] ], - ).toShadowContainer( - context, - ); + ).toShadowContainer(context); } Widget contactInfoCard(BuildContext context, WorkOrderData workOrder) { @@ -355,7 +353,7 @@ class _ServiceRequestDetailViewState extends State { InfoTextWidget(label: 'MR No'.addTranslation, value: provider.currentWorkOrder?.data?.mrNo?.toString() ?? '-'), InfoTextWidget(label: 'Exchange Cost'.addTranslation, value: provider.currentWorkOrder?.data?.exchangeCost?.toString() ?? '-'), ], - ).toShadowContainer(context, padding: 16); + ).toShadowContainer(context); } Widget assetRetiredButton({required BuildContext context}) { diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 3e8e0aad..d1535400 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -133,7 +133,7 @@ class _CreateIncidentRequestPageState extends State { onPick: (asset) async { device = asset; payload["assetId"] = asset?.id; - payload["oracleCode"] = asset.modelDefinition?.oracleCodes?.first.codeValue; + payload["oracleCode"] = (asset.modelDefinition?.oracleCodes?.isNotEmpty ?? false) ? asset.modelDefinition?.oracleCodes?.first.codeValue : null; payload["model"] = asset.modelDefinition?.modelName; payload["manufacturer"] = asset.modelDefinition?.manufacturerName; payload["assetOrigin"] = asset.modelDefinition?.assetName; diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index 854c098f..bba8588a 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -41,41 +41,39 @@ class IncidentDetailPage extends StatelessWidget { List allAttachments = snapshot.data!.incidentAttachments!; - return ListView( + return SingleChildScrollView( padding: const EdgeInsets.all(16), - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - StatusLabel( - label: snapshot.data!.incidentStatusName!, - textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), - backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), - ), - 1.width.expanded, - InfoDateWidget(snapshot.data!.createdDate?.toString() ?? ""), - ], - ), - // 12.height, - ...requesterDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...requestDetails(context, snapshot.data!), - if (snapshot.data!.ovrTicketNumber?.isNotEmpty ?? false) ovrInformation(context, snapshot.data!.ovrTicketNumber!), - const Divider().defaultStyle(context), - ...assetDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...personInvolvedDetails(context, snapshot.data!), - if (allAttachments.isNotEmpty) ...[ - const Divider().defaultStyle(context), - InfoHeader16Widget("Attachments".addTranslation), - FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: snapshot.data!.incidentStatusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), + ), + 1.width.expanded, + InfoDateWidget(snapshot.data!.createdDate?.toString() ?? ""), ], + ), + // 12.height, + ...requesterDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...requestDetails(context, snapshot.data!), + if (snapshot.data!.ovrTicketNumber?.isNotEmpty ?? false) ovrInformation(context, snapshot.data!.ovrTicketNumber!), + const Divider().defaultStyle(context), + ...assetDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...personInvolvedDetails(context, snapshot.data!), + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + InfoHeader16Widget("Attachments".addTranslation), + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], - ).toShadowContainer(context, padding: 16), - ], + ], + ).toShadowContainer(context), ); })); } @@ -98,7 +96,7 @@ class IncidentDetailPage extends StatelessWidget { text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), children: [ TextSpan( text: "${incidentData.workOrderNo}", - style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + style: AppTextStyles.bodyText.copyWith(color: AppColor.primary10, decoration: TextDecoration.underline), recognizer: TapGestureRecognizer() ..onTap = () async { await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CMDetailPage(requestId: incidentData.workOrderId!, moduleId: 3))); diff --git a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart index 6bc36ca2..0c5a54e1 100644 --- a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart +++ b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart @@ -98,7 +98,7 @@ class _LoanEquipmentDetailPageState extends State { FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], ], - ).toShadowContainer(context, padding: 12) + ).toShadowContainer(context) ], ).expanded, // @todo ask backend to add loanTypeValue, so we can show or hide the buttons diff --git a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart index 61b4e037..7e076c8f 100644 --- a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart +++ b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart @@ -106,7 +106,7 @@ class _MedicalGasInspectionDetailPageState extends State URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), ], ], - ).toShadowContainer(context, padding: 16), + ).toShadowContainer(context,), ], ).expanded, if (model?.requestStatus?.value != null && model!.requestStatus!.value! < 3) diff --git a/lib/new_views/pages/land_page/mark_attendance_widget.dart b/lib/new_views/pages/land_page/mark_attendance_widget.dart deleted file mode 100644 index 1ab12c67..00000000 --- a/lib/new_views/pages/land_page/mark_attendance_widget.dart +++ /dev/null @@ -1,866 +0,0 @@ -// import 'dart:async'; -// import 'dart:developer'; -// import 'dart:io'; -// -// import 'package:flutter/material.dart'; -// import 'package:flutter_svg/flutter_svg.dart'; -// import 'package:geolocator/geolocator.dart'; -// import 'package:huawei_location/huawei_location.dart'; -// import 'package:nfc_manager/nfc_manager.dart'; -// import 'package:permission_handler/permission_handler.dart'; -// import 'package:provider/provider.dart'; -// import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; -// // import 'package:platform_device_id/platform_device_id.dart'; -// import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; -// import 'package:test_sa/extensions/enum_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/enums/swipe_type.dart'; -// import 'package:test_sa/models/lookup.dart'; -// import 'package:test_sa/models/new_models/swipe_model.dart'; -// import 'package:test_sa/nfc/nfc_reader_sheet.dart'; -// import 'package:test_sa/utilities/Location.dart' as location; -// import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; -// import 'package:test_sa/views/widgets/dialogs/success_dialog.dart'; -// import 'package:test_sa/views/widgets/qr_scanner_dialog.dart'; -// import 'package:wifi_iot/wifi_iot.dart'; -// -// import '../../app_style/app_color.dart'; -// -// class MarkAttendanceWidget extends StatefulWidget { -// // DashboardProviderModel model; -// double topPadding; -// bool isFromDashboard; -// -// MarkAttendanceWidget( {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// // todo MarkAttendanceWidget(this.model, {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// @override -// _MarkAttendanceWidgetState createState() { -// return _MarkAttendanceWidgetState(); -// } -// } -// -// class _MarkAttendanceWidgetState extends State { -// bool isNfcEnabled = false, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false; -// -// int _locationUpdateCbId = 0; -// -// @override -// void initState() { -// super.initState(); -// checkAttendanceAvailability(); -// } -// -// void checkAttendanceAvailability() async { -// bool isAvailable = await NfcManager.instance.isAvailable(); -// // setState(() { -// // AppState().privilegeListModel!.forEach((PrivilegeListModel element) { -// // if (element.serviceName == "enableNFC") { -// // if (isAvailable) if (element.previlege ?? false) isNfcEnabled = true; -// // } else if (element.serviceName == "enableQR") { -// // if (element.previlege ?? false) isQrEnabled = true; -// // } else if (element.serviceName == "enableWIFI") { -// // if (element.previlege ?? false) isWifiEnabled = true; -// // } else if (element.serviceName!.trim() == "enableLocationNFC") { -// // if (element.previlege ?? false) isNfcLocationEnabled = true; -// // } else if (element.serviceName == "enableLocationQR") { -// // if (element.previlege ?? false) isQrLocationEnabled = true; -// // } else if (element.serviceName == "enableLocationWIFI") { -// // if (element.previlege ?? false) isWifiLocationEnabled = true; -// // } -// // }); -// // }); -// } -// -// void checkHuaweiLocationPermission(String attendanceType) async { -// // Permission_Handler permissionHandler = PermissionHandler(); -// location.Location.isEnabled((bool isEnabled) async { -// if (isEnabled) { -// location.Location.havePermission((bool permission) async { -// if (permission) { -// getHuaweiCurrentLocation(attendanceType); -// } else { -// bool has = await requestPermissions(); -// if (has) { -// getHuaweiCurrentLocation(attendanceType); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// -// // if (await permissionHandler.hasLocationPermission()) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // bool has = await requestPermissions(); -// // if (has) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } -// // } -// } -// -// Future requestPermissions() async { -// var result = await [ -// Permission.location, -// ].request(); -// return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); -// } -// -// @override -// void dispose() { -// super.dispose(); -// // Stop Session -// NfcManager.instance.stopSession(); -// } -// -// @override -// Widget build(BuildContext context) { -// return Container( -// padding: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: widget.topPadding), -// decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), -// width: double.infinity, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisSize: MainAxisSize.min, -// children: [ -// // LocaleKeys.markAttendance.tr().toSectionHeading(), -// // LocaleKeys.selectMethodOfAttendance.tr().toText11(color: const Color(0xff535353)), -// GridView( -// physics: const NeverScrollableScrollPhysics(), -// shrinkWrap: true, -// padding: const EdgeInsets.only(bottom: 0, top: 21), -// gridDelegate: -// SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 3 : 5, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), -// children: [ -// attendanceMethod(SwipeTypeEnum.NFC.name, Icons.nfc, true, () { -// log('i am here...nfc'); -// handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: true); -// return; -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("NFC"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("NFC", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// //if (isWifiEnabled) //todo -// attendanceMethod("Wifi", Icons.wifi, isWifiEnabled, () { -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("WIFI"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("WIFI", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performWifiAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// // if (isQrEnabled) //todo -// attendanceMethod(SwipeTypeEnum.QR.name, Icons.qr_code_2, true, () async { -// handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: true); -// return; -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("QR"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("QR", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performQrCodeAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// ], -// ) -// ], -// ), -// ); -// } -// -// void handleSwipe({required SwipeTypeEnum swipeType,required bool isEnable,}){ -// log('handle swipe value is ${swipeType.name}'); -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("NFC"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// handleSwipeOperation(swipeType: swipeType,lat: position.latitude,lang: position.longitude); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// } -// -// void handleSwipeOperation({required SwipeTypeEnum swipeType,double ?lat, double ?lang}){ -// switch(swipeType){ -// case SwipeTypeEnum.NFC: -// handleNfcAttendance(latitude: lat,longitude: lang); -// return; -// case SwipeTypeEnum.QR: -// performQrCodeAttendance(latitude: lat,longitude: lang); -// return; -// case SwipeTypeEnum.Wifi: -// return; -// -// } -// } -// -// void getHuaweiCurrentLocation(String attendanceType) async { -// try { -// Utils.showLoading(context); -// FusedLocationProviderClient locationService = FusedLocationProviderClient()..initFusedLocationService(); -// LocationRequest locationRequest = LocationRequest(); -// locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; -// locationRequest.interval = 500; -// List locationRequestList = [locationRequest]; -// LocationSettingsRequest locationSettingsRequest = LocationSettingsRequest(requests: locationRequestList); -// -// late StreamSubscription _streamSubscription; -// int requestCode = (await (locationService.requestLocationUpdates(locationRequest)))!; -// -// _streamSubscription = locationService.onLocationData!.listen( -// (Location location) async { -// Utils.hideLoading(context); -// await locationService.removeLocationUpdates(requestCode); -// if (attendanceType == "QR") { -// // todo performQrCodeAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// if (attendanceType == "WIFI") { -// // todo performWifiAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// if (attendanceType == "NFC") { -// //todo performNfcAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// requestCode = 0; -// }, -// ); -// -// // locationService.checkLocationSettings(locationSettingsRequest).then((settings) async { -// // await locationService.getLastLocation().then((value) { -// // if (value.latitude == null || value.longitude == null) { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "Unable to get your location, Please check your location settings & try again.", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // if (attendanceType == "QR") { -// // performQrCodeAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "WIFI") { -// // performWifiAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "NFC") { -// // performNfcAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // } -// // }).catchError((error) { -// // log("HUAWEI LOCATION getLastLocation ERROR!!!!!"); -// // log(error); -// // }); -// // }).catchError((error) { -// // log("HUAWEI LOCATION checkLocationSettings ERROR!!!!!"); -// // log(error); -// // if (error.code == "LOCATION_SETTINGS_NOT_AVAILABLE") { -// // // Location service not enabled. -// // } -// // }); -// } catch (error) { -// log("HUAWEI LOCATION ERROR!!!!!"); -// log('$error'); -// Utils.hideLoading(context); -// // Utils.handleException(error, context, null); -// } -// } -// -// Future handleNfcAttendance({double? latitude = 0, double? longitude = 0}) async { -// final dashBoardProvider = Provider.of(context, listen: false); -// -// if (Platform.isIOS) { -// Utils.readNFc(onRead: (String nfcId) async { -// await _processNfcAttendance(dashBoardProvider, nfcId, latitude, longitude); -// }); -// } else { -// showNfcReader(context, onNcfScan: (String? nfcId) async { -// await _processNfcAttendance(dashBoardProvider, nfcId ?? '', latitude, longitude); -// }); -// } -// } -// -// -// Future _processNfcAttendance( -// DashBoardProvider dashBoardProvider, -// String nfcId, -// double? latitude, -// double? longitude, -// ) async { -// Utils.showLoading(context); -// try { -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), -// value: nfcId, -// latitude: latitude, -// longitude: longitude, -// ); -// -// final swipeResponse = await dashBoardProvider.makeSwipe(model: swipeModel); -// log('swipe response i got is ${swipeResponse.toJson()}'); -// -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// _showErrorDialog(swipeResponse.message ?? "Unexpected error occurred"); -// } else { -// final isSuccess = swipeResponse.data; -// log('nfc swipe response is ${isSuccess}'); -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// _showSuccessDialog(); -// } -// } catch (error) { -// Utils.hideLoading(context); -// // Uncomment below line for error handling if needed -// // Utils.handleException(error, context, null); -// } -// } -// -// -// void _showErrorDialog(String message) { -// showDialog( -// context: context, -// builder: (context) => ConfirmDialog( -// message: message, -// onTap: () => Navigator.pop(context), -// ), -// ); -// } -// -// void _showSuccessDialog() { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// -// -// //older code.... -// Future performNfcAttendance({double? lat = 0, double ?lng = 0}) async { -// DashBoardProvider dashBoardProvider = Provider.of(context,listen:false); -// if (Platform.isIOS) { -// Utils.readNFc(onRead: (String nfcId) async { -// Utils.showLoading(context); -// try { -// SwipeModel? swipeResponse = await dashBoardProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '', latitude: lat, longitude: lng)); -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "Unexpected error occurred", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } else { -// bool status = swipeResponse.data; -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// } -// }); -// } else { -// showNfcReader(context, onNcfScan: (String? nfcId) async { -// Utils.showLoading(context); -// try { -// SwipeModel? swipeResponse = await dashBoardProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: nfcId??'', latitude: lat, longitude: lng)); -// log('api response i got is ${swipeResponse.toJson()}'); -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "Unexpected error occurred", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } else { -// bool status = swipeResponse.data; //use this status to get transactions. -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// } -// // Utils.showLoading(context); -// // try { -// // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); -// // if (g?.messageStatus != 1) { -// // Utils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: g?.errorEndUserMessage ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = await model.fetchAttendanceTracking(context); -// // Utils.hideLoading(context); -// // showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: false, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // log(ex); -// // Utils.hideLoading(context); -// // // Utils.handleException(ex, context, (String msg) { -// // // Utils.confirmDialog(context, msg); -// // // }); -// // } -// }); -// } -// } -// -// -// -// void showMDialog(context, {Widget? child, Color? backgroundColor, bool isDismissable = true, bool isBusniessCard = false}) async { -// return showDialog( -// context: context, -// barrierDismissible: isDismissable, -// builder: (context) { -// return Dialog( -// shape: isBusniessCard -// ? const RoundedRectangleBorder( -// borderRadius: BorderRadius.all( -// Radius.circular(15.0), -// ), -// ) -// : null, -// backgroundColor: backgroundColor, -// child: child, -// ); -// }, -// ); -// } -// // -// // Future checkSession() async { -// // try { -// // Utils.showLoading(context); -// // await DashboardApiClient().getOpenMissingSwipes(); -// // Utils.hideLoading(context); -// // return true; -// // } catch (ex) { -// // Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// // return false; -// // } -// // } -// -// -// //TODO need to confirm .... -// // Future performWifiAttendance({double? latitude, double? lng}) async { -// // // if (Platform.isAndroid) { -// // // if (!(await checkSession())) { -// // // return; -// // // } -// // // } -// // Utils.showLoading(context); -// // bool isConnected = await WiFiForIoTPlugin.connect(AppState().getMohemmWifiSSID ?? "", -// // password: AppState().getMohemmWifiPassword ?? "", joinOnce: Platform.isIOS ? false : true, security: NetworkSecurity.WPA, withInternet: false); -// // -// // if (Platform.isIOS) { -// // if (await WiFiForIoTPlugin.getSSID() == AppState().getMohemmWifiSSID) { -// // isConnected = true; -// // } else { -// // isConnected = false; -// // } -// // } -// // -// // if (isConnected && AppState().isAuthenticated) { -// // await WiFiForIoTPlugin.forceWifiUsage(true); -// // await Future.delayed(const Duration(seconds: 6)); -// // try { -// // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 3, nfcValue: "", isGpsRequired: isWifiLocationEnabled, lat: lat, long: lng); -// // bool status = await model.fetchAttendanceTracking(context); -// // Utils.hideLoading(context); -// // await closeWifiRequest(); -// // if (g?.messageStatus == 2) { -// // showDialog( -// // barrierDismissible: true, -// // context: context, -// // builder: (cxt) => ConfirmDialog( -// // message: g?.errorEndUserMessage ?? "", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // onCloseTap: () {}, -// // ), -// // ); -// // } else { -// // showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: false, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // await closeWifiRequest(); -// // Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// // } -// // } else { -// // if (AppState().isAuthenticated) { -// // Utils.hideLoading(context); -// // Utils.confirmDialog(context, "LocaleKeys.comeNearHMGWifi.tr()"); -// // } else { -// // await closeWifiRequest(); -// // } -// // } -// // } -// -// Future closeWifiRequest() async { -// if (Platform.isAndroid) { -// await WiFiForIoTPlugin.forceWifiUsage(false); -// } -// return await WiFiForIoTPlugin.disconnect(); -// } -// -// Future performQrCodeAttendance( {double ? latitude , double ?longitude}) async { -// DashBoardProvider dashBoardProvider = Provider.of(context,listen: false); -// var qrCodeValue = await Navigator.of(context).push( -// MaterialPageRoute( -// builder: (BuildContext context) => QrScannerDialog(), -// ), -// ); -// if (qrCodeValue != null) { -// Utils.showLoading(context); -// try { -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), -// value: qrCodeValue, -// latitude: latitude, -// longitude: longitude, -// ); -// log('model i got to scan qr is ${swipeModel.toJson()}'); -// final swipeResponse = await dashBoardProvider.makeSwipe(model: swipeModel); -// log('response of swipe is ${swipeResponse.toJson()}'); -// -// bool status = await swipeResponse.data; -// Utils.hideLoading(context); -// if (swipeResponse.responseCode == 2) { -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {}, -// ), -// ); -// } else { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// log('$ex'); -// Utils.hideLoading(context); -// //this need to confirm where it comes.. -// // Utils.handleException(ex, context, null); -// } -// } -// } -// -// void markFakeAttendance(dynamic sourceName, String lat, String long) async { -// Utils.showLoading(context); -// try { -// // await DashboardApiClient().markFakeLocation(sourceName: sourceName, lat: lat, long: long); -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "LocaleKeys.fakeLocation.tr()"); -// } catch (ex) { -// log('$ex'); -// Utils.hideLoading(context); -// //Utils.handleException(ex, context, null); -// } -// } -// -// Widget attendanceMethod(String title, IconData iconData, bool isEnabled, VoidCallback onPress) => Container( -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(15), -// color: isEnabled ? null : Colors.grey.withOpacity(.5), -// gradient: isEnabled -// ? const LinearGradient( -// transform: GradientRotation(.64), -// begin: Alignment.topRight, -// end: Alignment.bottomLeft, -// colors: [ -// //ToDo set Colors according to design provided by designer... -// Colors.blue, -// Colors.green, -// // AppColor.gradiantEndColor, -// // MyColors.gradiantStartColor, -// ], -// ) -// : null, -// ), -// clipBehavior: Clip.antiAlias, -// padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// // SvgPicture.asset(image, color: Colors.white, alignment: Alignment.topLeft).expanded, -// Icon(iconData, color: isEnabled ? AppColor.black35 : Colors.grey), -// title.heading6(context), -// // title.toText17(isBold: true, color: Colors.white), -// ], -// ), -// ).onPress( -// () { -// log('isEnabled is ${!isEnabled}'); -// if (!isEnabled) return; -// onPress(); -// }, -// ); -// } 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 d1540eba..ee227136 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 @@ -200,10 +200,11 @@ class _MyRequestsPageState extends State with SingleTickerProvid setState(() {}); } }).expanded, - 16.width, + 12.width, IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: () async { final provider = Provider.of(context, listen: false); @@ -223,6 +224,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid IconButton( icon: "filter".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => AllRequestsFilterPage(data: _search))); @@ -233,7 +235,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid }, ), ], - ).paddingOnly(start: 16, end: 16), + ).paddingOnly(start: 16, end: 8), ), body: Consumer(builder: (context, snapshot, _) { return Column( diff --git a/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart b/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart index a26c7965..1e0cc7f0 100644 --- a/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart +++ b/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart @@ -4,6 +4,7 @@ import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; class NonHmgEmployeeSwipeView extends StatefulWidget { NonHmgEmployeeSwipeView({Key? key}) : super(key: key); @@ -47,6 +48,7 @@ class _NonHmgEmployeeSwipeViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + InfoHeader16Widget( "Mark Attendance"), Text( "Mark Attendance", style: AppTextStyles.heading5.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), diff --git a/lib/new_views/pages/login_page.dart b/lib/new_views/pages/login_page.dart index 1c6a83bd..4f8f7d7b 100644 --- a/lib/new_views/pages/login_page.dart +++ b/lib/new_views/pages/login_page.dart @@ -180,7 +180,7 @@ class _LoginPageState extends State { children: [ Checkbox( value: rememberMe, - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -190,7 +190,7 @@ class _LoginPageState extends State { }); }), 8.width, - "Remember Me".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50).expanded, + "Remember Me".bodyText(context).custom(color: AppColor.textStyleColor(context)).expanded, ], ), 8.height, @@ -200,7 +200,7 @@ class _LoginPageState extends State { Checkbox( value: privacyPolicyChecked, side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { @@ -210,10 +210,10 @@ class _LoginPageState extends State { }), 8.width, RichText( - text: TextSpan(text: "I have read and agree to ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), children: [ + text: TextSpan(text: "I have read and agree to ", style: AppTextStyles.bodyText.copyWith(color: AppColor.textStyleColor(context)), children: [ TextSpan( text: "Privacy Policy", - style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + style: AppTextStyles.bodyText.copyWith(color: AppColor.primary10, decoration: TextDecoration.underline), recognizer: TapGestureRecognizer() ..onTap = () async { Uri uri = Uri.parse("https://cloudsolutions.com.sa/en/privacy-policy"); diff --git a/lib/new_views/swipe_module/mark_attendance_widget.dart b/lib/new_views/swipe_module/mark_attendance_widget.dart deleted file mode 100644 index 7ebe6b6a..00000000 --- a/lib/new_views/swipe_module/mark_attendance_widget.dart +++ /dev/null @@ -1,821 +0,0 @@ -// import 'dart:async'; -// import 'dart:developer'; -// import 'dart:io'; -// -// import 'package:flutter/material.dart'; -// import 'package:geolocator/geolocator.dart'; -// import 'package:huawei_location/huawei_location.dart'; -// import 'package:nfc_manager/nfc_manager.dart'; -// import 'package:permission_handler/permission_handler.dart'; -// import 'package:provider/provider.dart'; -// import 'package:test_sa/controllers/providers/api/user_provider.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/new_views/app_style/app_color.dart'; -// import 'package:test_sa/new_views/pages/land_page/nfc/nfc_reader_sheet.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/qr_scanner_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/success_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; -// import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; -// import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; -// import 'package:test_sa/new_views/swipe_module/utils/location_utils.dart'; -// import 'package:test_sa/new_views/swipe_module/utils/swipe_general_utils.dart'; -// import 'package:wifi_iot/wifi_iot.dart'; -// todo @sikander delete this file after compelte -// class MarkAttendanceWidget extends StatefulWidget { -// double topPadding; -// bool isFromDashboard; -// -// MarkAttendanceWidget({Key key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// // todo MarkAttendanceWidget(this.model, {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// @override -// _MarkAttendanceWidgetState createState() { -// return _MarkAttendanceWidgetState(); -// } -// } -// -// class _MarkAttendanceWidgetState extends State { -// bool isNfcEnabled = false, isQrEnabled = false, isWifiEnabled = false; -// UserProvider _userProvider; -// -// int _locationUpdateCbId = 0; -// -// @override -// void initState() { -// super.initState(); -// checkAttendanceAvailability(); -// } -// -// void checkAttendanceAvailability() async { -// _userProvider = Provider.of(context, listen: false); -// bool isAvailable = await NfcManager.instance.isAvailable(); -// log('backend enabled values are nfc ${_userProvider.user.enableNFC} qr ${_userProvider.user.enableQR} wifi ${_userProvider.user.enableWifi}'); -// setState(() { -// if (isAvailable && _userProvider.user.enableNFC) isNfcEnabled = true; -// if (_userProvider.user.enableQR) isQrEnabled = true; -// if (_userProvider.user.enableWifi) isWifiEnabled = true; -// }); -// } -// -// void checkHuaweiLocationPermission(SwipeTypeEnum attendanceType, BuildContext context) async { -// // Permission_Handler permissionHandler = PermissionHandler(); -// LocationUtilities.isEnabled((bool isEnabled) async { -// if (isEnabled) { -// LocationUtilities.havePermission((bool permission) async { -// if (permission) { -// getHuaweiCurrentLocation(attendanceType, context); -// } else { -// bool has = await requestPermissions(); -// if (has) { -// getHuaweiCurrentLocation(attendanceType, context); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// -// // if (await permissionHandler.hasLocationPermission()) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // bool has = await requestPermissions(); -// // if (has) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } -// // } -// } -// -// Future requestPermissions() async { -// var result = await [ -// Permission.location, -// ].request(); -// return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); -// } -// -// @override -// void dispose() { -// super.dispose(); -// // Stop Session -// NfcManager.instance.stopSession(); -// } -// -// @override -// Widget build(BuildContext context) { -// return Container( -// padding: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: widget.topPadding), -// decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), -// width: double.infinity, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisSize: MainAxisSize.min, -// children: [ -// 20.height, -// 'click me '.heading5(context).custom(color: AppColor.textStyleColor(context)), -// // LocaleKeys.markAttendance.tr().toSectionHeading(), -// // LocaleKeys.selectMethodOfAttendance.tr().toText11(color: const Color(0xff535353)), -// GridView( -// physics: const NeverScrollableScrollPhysics(), -// shrinkWrap: true, -// padding: const EdgeInsets.only(bottom: 0, top: 21), -// gridDelegate: -// SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 3 : 5, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), -// children: availableAttendanceMethodList(context: context), -// ) -// ], -// ), -// ); -// } -// -// void handleSwipe({ -// SwipeTypeEnum swipeType, -// @required bool isEnable, -// @required BuildContext context, -// }) { -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission(swipeType, context); -// } else { -// LocationUtilities.isEnabled((bool isEnabled) { -// if (isEnabled) { -// LocationUtilities.havePermission((bool permission) { -// if (permission) { -// SwipeGeneralUtils.showLoading(context); -// LocationUtilities.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? "", context); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// handleSwipeOperation(swipeType: swipeType, lat: position.latitude, lang: position.longitude); -// } -// }, -// () { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// } -// -// void handleSwipeOperation({@required SwipeTypeEnum swipeType, double lat, double lang, BuildContext context}) { -// switch (swipeType) { -// case SwipeTypeEnum.NFC: -// handleNfcAttendance(latitude: lat, longitude: lang, context: context); -// return; -// case SwipeTypeEnum.QR: -// performQrCodeAttendance(latitude: lat, longitude: lang, context: context); -// return; -// case SwipeTypeEnum.Wifi: -// //TODO need to implement. -// return; -// } -// } -// -// void getHuaweiCurrentLocation(SwipeTypeEnum attendanceType, BuildContext context) async { -// try { -// SwipeGeneralUtils.showLoading(context); -// FusedLocationProviderClient locationService = FusedLocationProviderClient()..initFusedLocationService(); -// LocationRequest locationRequest = LocationRequest(); -// locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; -// locationRequest.interval = 500; -// List locationRequestList = [locationRequest]; -// LocationSettingsRequest locationSettingsRequest = LocationSettingsRequest(requests: locationRequestList); -// -// StreamSubscription _streamSubscription; -// int requestCode = (await (locationService.requestLocationUpdates(locationRequest))); -// -// _streamSubscription = locationService.onLocationData.listen( -// (Location location) async { -// SwipeGeneralUtils.hideLoading(context); -// await locationService.removeLocationUpdates(requestCode); -// handleSwipeOperation(swipeType: attendanceType); -// requestCode = 0; -// }, -// ); -// -// // locationService.checkLocationSettings(locationSettingsRequest).then((settings) async { -// // await locationService.getLastLocation().then((value) { -// // if (value.latitude == null || value.longitude == null) { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "Unable to get your location, Please check your location settings & try again.", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // if (attendanceType == "QR") { -// // performQrCodeAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "WIFI") { -// // performWifiAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "NFC") { -// // performNfcAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // } -// // }).catchError((error) { -// // log("HUAWEI LOCATION getLastLocation ERROR!!!!!"); -// // log(error); -// // }); -// // }).catchError((error) { -// // log("HUAWEI LOCATION checkLocationSettings ERROR!!!!!"); -// // log(error); -// // if (error.code == "LOCATION_SETTINGS_NOT_AVAILABLE") { -// // // Location service not enabled. -// // } -// // }); -// } catch (error) { -// log("HUAWEI LOCATION ERROR!!!!!"); -// log('$error'); -// SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.handleException(error, context, null); -// } -// } -// -// Future handleNfcAttendance({double latitude = 0, double longitude = 0, BuildContext context}) async { -// final userProvider = Provider.of(context, listen: false); -// -// if (Platform.isIOS) { -// SwipeGeneralUtils.readNFc(onRead: (String nfcId) async { -// await _processNfcAttendance(userProvider, nfcId, latitude, longitude, context); -// }); -// } else { -// showNfcReader(context, onNcfScan: (String nfcId) async { -// await _processNfcAttendance(userProvider, nfcId ?? '', latitude, longitude, context); -// }); -// } -// } -// -// Future _processNfcAttendance( -// UserProvider userProvider, -// String nfcId, -// double latitude, -// double longitude, -// BuildContext context, -// ) async { -// SwipeGeneralUtils.showLoading(context); -// try { -// // final swipeModel = Swipe( -// // swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), -// // value: nfcId, -// // latitude: latitude, -// // longitude: longitude, -// // ); -// //Test model... -// final swipeModel = Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '123', latitude: 24.70865415364271, longitude: 46.66600861881879); -// final swipeResponse = await userProvider.makeSwipe(model: swipeModel); -// if (swipeResponse.isSuccess) { -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// SwipeGeneralUtils.hideLoading(context); -// Navigator.pushNamed(context, SwipeSuccessView.routeName); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.showErrorDialog(message: swipeResponse.message ?? "Unexpected error occurred", context: context); -// } -// } catch (errSwipeGeneralUtilsor) { -// SwipeGeneralUtils.hideLoading(context); -// // Uncomment below line for error handling if needed -// // SwipeGeneralUtils.handleException(error, context, null); -// } -// } -// -// Future closeWifiRequest() async { -// if (Platform.isAndroid) { -// await WiFiForIoTPlugin.forceWifiUsage(false); -// } -// return await WiFiForIoTPlugin.disconnect(); -// } -// -// Future performQrCodeAttendance({double latitude, double longitude, BuildContext context}) async { -// UserProvider userProvider = Provider.of(context, listen: false); -// var qrCodeValue = await Navigator.of(context).push( -// MaterialPageRoute( -// builder: (BuildContext context) => QrScannerDialog(), -// ), -// ); -// if (qrCodeValue != null) { -// SwipeGeneralUtils.showLoading(context); -// try { -// //test model.. -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), -// value: '2323', -// latitude: 24.70865415364271, -// longitude: 46.66600861881879, -// ); -// final swipeResponse = await userProvider.makeSwipe(model: swipeModel); -// if (swipeResponse.isSuccess) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.showMDialog(context, backgroundColor: Colors.transparent, isDismissable: true, child: SuccessDialog(widget.isFromDashboard)); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {} -// ), -// ); -// } -// } catch (ex) { -// SwipeGeneralUtils.hideLoading(context); -// } -// } -// } -// -// List availableAttendanceMethodList({@required BuildContext context}) { -// List availableMethods = []; -// if (isNfcEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.NFC.name, 'nfc_icon', isNfcEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: isNfcEnabled, context: context); -// })); -// } -// if (isQrEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.QR.name, 'wifi_icon', isQrEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: isQrEnabled, context: context); -// })); -// } -// if (isWifiEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.Wifi.name, 'wifi_icon', isWifiEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.Wifi, isEnable: isWifiEnabled, context: context); -// })); -// } -// return availableMethods; -// } -// -// Widget attendanceMethod(String title, String icon, bool isEnabled, VoidCallback onPress) { -// return Container( -// padding: EdgeInsets.all(12), -// decoration: BoxDecoration( -// color: Colors.white, -// borderRadius: BorderRadius.circular(18), -// border: Border.all(color: AppColor.white40, width: 2), -// ), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// icon.toSvgAsset(), -// title.heading5(context).custom(color: AppColor.neutral50), -// // Text( -// // title, -// // style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), -// // ), -// ], -// ), -// ).onPress( -// () { -// log('isEnabled is ${!isEnabled}'); -// if (!isEnabled) return; -// onPress(); -// }, -// ); -// // return Container( -// // decoration: BoxDecoration( -// // borderRadius: BorderRadius.circular(15), -// // color: isEnabled ? null : Colors.grey.withOpacity(.5), -// // gradient: isEnabled -// // ? const LinearGradient( -// // transform: GradientRotation(.64), -// // begin: Alignment.topRight, -// // end: Alignment.bottomLeft, -// // colors: [ -// // //ToDo set Colors according to design provided by designer... -// // Colors.blue, -// // Colors.green, -// // // AppColor.gradiantEndColor, -// // // MyColors.gradiantStartColor, -// // ], -// // ) -// // : null, -// // ), -// // clipBehavior: Clip.antiAlias, -// // padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), -// // child: Column( -// // crossAxisAlignment: CrossAxisAlignment.start, -// // children: [ -// // // SvgPicture.asset(image, color: Colors.white, alignment: Alignment.topLeft).expanded, -// // Icon(iconData, color: isEnabled ? AppColor.black35 : Colors.grey), -// // title.heading6(context), -// // // title.toText17(isBold: true, color: Colors.white), -// // ], -// // ), -// // ).onPress( -// // () { -// // log('isEnabled is ${!isEnabled}'); -// // if (!isEnabled) return; -// // onPress(); -// // }, -// // ); -// } -// } -// -// // Widget customListItem({required String icon, required String heading, required String subHeading, required VoidCallback onTap}) { -// // return GestureDetector( -// // onTap: onTap, // Handles the tap -// // child: Card( -// // shape: RoundedRectangleBorder( -// // borderRadius: BorderRadius.circular(14), // Circular border radius -// // ), -// // color: Colors.white, -// // child: Row( -// // crossAxisAlignment: CrossAxisAlignment.start, // Align items at the top -// // children: [ -// // // Icon Section -// // icon -// // .toSvgAsset( -// // width: 32, -// // color: AppColor.neutral120, -// // height: 29, -// // ) -// // .paddingOnly(top: 8), -// // 14.width, -// // Expanded( -// // child: Column( -// // crossAxisAlignment: CrossAxisAlignment.start, -// // children: [ -// // Text( -// // heading, -// // style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50), -// // ), -// // 7.height, -// // Text( -// // subHeading, -// // style: AppTextStyles.bodyText2.copyWith(color: AppColor.neutral120), -// // ), -// // ], -// // ).paddingOnly(end: 50), -// // ), -// // ], -// // ).paddingAll(12), -// // ), -// // ); -// // } -// -// //qr older code... -// -// //nfc older code widget -// -// // // if (AppState().getIsHuawei) { -// // if (false) { -// // checkHuaweiLocationPermission(SwipeTypeEnum.NFC); -// // } else { -// // LocationUtilities.isEnabled((bool isEnabled) { -// // if (isEnabled) { -// // LocationUtilities.havePermission((bool permission) { -// // if (permission) { -// // SwipeGeneralUtils.showLoading(context); -// // LocationUtilities.getCurrentLocation( -// // (Position position, bool isMocked) { -// // if (isMocked) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.markFakeAttendance("NFC", position.latitude.toString() ?? "", position.longitude.toString() ?? "",context); -// // } else { -// // SwipeGeneralUtils.hideLoading(context); -// // //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// // } -// // }, -// // () { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// // }, -// // context, -// // ); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openAppSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to enable location services to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openLocationSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } -// -// //older code.... -// // Future performNfcAttendance({double lat = 0, double lng = 0}) async { -// // UserProvider userProvider = Provider.of(context,listen:false); -// // if (Platform.isIOS) { -// // SwipeGeneralUtils.readNFc(onRead: (String nfcId) async { -// // SwipeGeneralUtils.showLoading(context); -// // try { -// // SwipeModel swipeResponse = await userProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '', latitude: lat, longitude: lng)); -// // if (swipeResponse.responseCode != 1) { -// // SwipeGeneralUtils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: swipeResponse.message ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = swipeResponse.data; -// // if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: true, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // // SwipeGeneralUtils.handleException(ex, context, null); -// // } -// // }); -// // } else { -// // showNfcReader(context, onNcfScan: (String nfcId) async { -// // SwipeGeneralUtils.showLoading(context); -// // try { -// // SwipeModel swipeResponse = await userProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: nfcId??'', latitude: lat, longitude: lng)); -// // log('api response i got is ${swipeResponse.toJson()}'); -// // if (swipeResponse.responseCode != 1) { -// // SwipeGeneralUtils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: swipeResponse.message ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = swipeResponse.data; //use this status to get transactions. -// // if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: true, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // // SwipeGeneralUtils.handleException(ex, context, null); -// // } -// // // SwipeGeneralUtils.showLoading(context); -// // // try { -// // // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); -// // // if (g?.messageStatus != 1) { -// // // SwipeGeneralUtils.hideLoading(context); -// // // showDialog( -// // // context: context, -// // // builder: (BuildContext cxt) => ConfirmDialog( -// // // message: g?.errorEndUserMessage ?? "Unexpected error occurred", -// // // onTap: () { -// // // Navigator.pop(context); -// // // }, -// // // ), -// // // ); -// // // } else { -// // // bool status = await model.fetchAttendanceTracking(context); -// // // SwipeGeneralUtils.hideLoading(context); -// // // showMDialog( -// // // context, -// // // backgroundColor: Colors.transparent, -// // // isDismissable: false, -// // // child: SuccessDialog(widget.isFromDashboard), -// // // ); -// // // } -// // // } catch (ex) { -// // // log(ex); -// // // SwipeGeneralUtils.hideLoading(context); -// // // // SwipeGeneralUtils.handleException(ex, context, (String msg) { -// // // // SwipeGeneralUtils.confirmDialog(context, msg); -// // // // }); -// // // } -// // }); -// // } -// // } -// -// // -// // Future checkSession() async { -// // try { -// // SwipeGeneralUtils.showLoading(context); -// // await DashboardApiClient().getOpenMissingSwipes(); -// // SwipeGeneralUtils.hideLoading(context); -// // return true; -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.handleException(ex, context, null); -// // return false; -// // } -// // } -// -// //TODO need to confirm .... -// Future performWifiAttendance({double? latitude, double? lng}) async { -// // if (Platform.isAndroid) { -// // if (!(await checkSession())) { -// // return; -// // } -// // } -// SwipeGeneralUtils.showLoading(context); -// bool isConnected = await WiFiForIoTPlugin.connect(AppState().getMohemmWifiSSID ?? "", -// password: AppState().getMohemmWifiPassword ?? "", joinOnce: Platform.isIOS ? false : true, security: NetworkSecurity.WPA, withInternet: false); -// -// if (Platform.isIOS) { -// if (await WiFiForIoTPlugin.getSSID() == AppState().getMohemmWifiSSID) { -// isConnected = true; -// } else { -// isConnected = false; -// } -// } -// -// if (isConnected && AppState().isAuthenticated) { -// await WiFiForIoTPlugin.forceWifiUsage(true); -// await Future.delayed(const Duration(seconds: 6)); -// try { -// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 3, nfcValue: "", isGpsRequired: isWifiLocationEnabled, lat: lat, long: lng); -// bool status = await model.fetchAttendanceTracking(context); -// SwipeGeneralUtils.hideLoading(context); -// await closeWifiRequest(); -// if (g?.messageStatus == 2) { -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: g?.errorEndUserMessage ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {}, -// ), -// ); -// } else { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: false, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// await closeWifiRequest(); -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.handleException(ex, context, null); -// } -// } else { -// if (AppState().isAuthenticated) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.confirmDialog(context, "LocaleKeys.comeNearHMGWifi.tr()"); -// } else { -// await closeWifiRequest(); -// } -// } -// } -// -// // older grid widget.. -// // attendanceMethod(SwipeTypeEnum.NFC.name, Icons.nfc, isNfcEnabled, () { -// // handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: isNfcEnabled); -// // }), -// // attendanceMethod(SwipeTypeEnum.QR.name, Icons.qr_code_2, isQrEnabled, () async { -// // handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: true); -// // }), -// // //if (isWifiEnabled) //todo -// // attendanceMethod(SwipeTypeEnum.Wifi.name, Icons.wifi, isWifiEnabled, () { -// // // if (AppState().getIsHuawei) { -// // if (false) { -// // checkHuaweiLocationPermission(SwipeTypeEnum.Wifi); -// // } else { -// // LocationUtilities.isEnabled((bool isEnabled) { -// // if (isEnabled) { -// // LocationUtilities.havePermission((bool permission) { -// // if (permission) { -// // SwipeGeneralUtils.showLoading(context); -// // LocationUtilities.getCurrentLocation( -// // (Position position, bool isMocked) { -// // if (isMocked) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.markFakeAttendance("WIFI", position.latitude.toString() ?? "", position.longitude.toString() ?? "", context); -// // } else { -// // SwipeGeneralUtils.hideLoading(context); -// // //todo performWifiAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// // } -// // }, -// // () { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// // }, -// // context, -// // ); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openAppSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to enable location services to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openLocationSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } -// // }), diff --git a/lib/new_views/swipe_module/swipe_history_view.dart b/lib/new_views/swipe_module/swipe_history_view.dart index c340f7bb..3a939898 100644 --- a/lib/new_views/swipe_module/swipe_history_view.dart +++ b/lib/new_views/swipe_module/swipe_history_view.dart @@ -47,87 +47,86 @@ class _SwipeHistoryViewState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: const DefaultAppBar( - title: 'Swipe History', - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 8.height, - Row( - children: [ - ADatePicker( - label: context.translation.from, - date: dateFrom, - from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), - formatDateWithTime: true, - onDatePicker: (selectedDate) { - if (selectedDate != null) { - showThemedTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ).then((selectedTime) { - // Handle the selected date and time here. - if (selectedTime != null) { - DateTime selectedDateTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - if (selectedDateTime != null) { - setState(() { - dateFrom = selectedDateTime; - }); + appBar: const DefaultAppBar(title: 'Swipe History'), + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8, + children: [ + ADatePicker( + label: context.translation.from, + date: dateFrom, + from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), + formatDateWithTime: true, + onDatePicker: (selectedDate) { + if (selectedDate != null) { + showThemedTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ).then((selectedTime) { + // Handle the selected date and time here. + if (selectedTime != null) { + DateTime selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + if (selectedDateTime != null) { + setState(() { + dateFrom = selectedDateTime; + }); + } } - } - }); - } - }, - ).expanded, - 8.width, - ADatePicker( - label: context.translation.to, - date: dateTo, - from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), - formatDateWithTime: true, - onDatePicker: (selectedDate) { - if (selectedDate != null) { - showThemedTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ).then((selectedTime) { - // Handle the selected date and time here. - if (selectedTime != null) { - DateTime selectedDateTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - if (selectedDateTime != null) { - setState(() { - dateTo = selectedDateTime; - }); + }); + } + }, + ).expanded, + ADatePicker( + label: context.translation.to, + date: dateTo, + from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), + formatDateWithTime: true, + onDatePicker: (selectedDate) { + if (selectedDate != null) { + showThemedTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ).then((selectedTime) { + // Handle the selected date and time here. + if (selectedTime != null) { + DateTime selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + if (selectedDateTime != null) { + setState(() { + dateTo = selectedDateTime; + }); + } } - } - }); - } - }, - ).expanded, - ], - ), - 12.height, - AppFilledButton(label: context.translation.search, maxWidth: false, onPressed: getSwipeHistory), - 8.height, - const Divider().defaultStyle(context), - Consumer(builder: (context, snapshot, child) { - return SwipeHistoryList(snapshot.swipeHistory ?? [], snapshot.isLoading).expanded; - }), - ], - ).paddingAll(20), + }); + } + }, + ).expanded, + ], + ), + 12.height, + AppFilledButton(label: context.translation.search, maxWidth: false, onPressed: getSwipeHistory), + 8.height, + const Divider().defaultStyle(context), + Consumer(builder: (context, snapshot, child) { + return SwipeHistoryList(snapshot.swipeHistory ?? [], snapshot.isLoading).expanded; + }).expanded, + ], + ).paddingOnly(start: 16, end: 16, top: 16), + ), ); } } @@ -143,7 +142,7 @@ class SwipeHistoryList extends StatelessWidget { return (list.isEmpty && !isLoading) ? NoDataFound(message: context.translation.noDataFound).center : ListView.separated( - padding: EdgeInsets.only(top: 12.toScreenHeight), + padding: const EdgeInsets.only(bottom: 16, top: 16), itemBuilder: (cxt, index) { if (isLoading) return const SizedBox().toRequestShimmer(cxt, isLoading); return SwipeHistoryCard(list[index]); diff --git a/lib/new_views/swipe_module/utils/swipe_general_utils.dart b/lib/new_views/swipe_module/utils/swipe_general_utils.dart index a24834e0..ac6c7c0f 100644 --- a/lib/new_views/swipe_module/utils/swipe_general_utils.dart +++ b/lib/new_views/swipe_module/utils/swipe_general_utils.dart @@ -29,6 +29,8 @@ import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; import 'package:test_sa/new_views/swipe_module/utils/location_utils.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/qr/scan_qr.dart'; import 'package:wifi_iot/wifi_iot.dart'; @@ -125,18 +127,8 @@ class SwipeGeneralUtils { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - icon.toSvgAsset( - color: isEnabled - ? context.isDark - ? AppColor.neutral30 - : AppColor.neutral50 - : Colors.grey.withOpacity(0.5)), - title.heading5(context).custom( - color: isEnabled - ? context.isDark - ? AppColor.neutral30 - : AppColor.neutral50 - : Colors.grey.withOpacity(0.5)), + icon.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), + title.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), ], ), ).onPress( @@ -826,9 +818,9 @@ class _SwipeTypeBottomSheetContentState extends State<_SwipeTypeBottomSheetConte mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - context.translation.markAttendance.heading5(context).custom(color: AppColor.headingTextColor(context)), + InfoHeader16Widget("Mark Attendance"), 8.height, - context.translation.selectMethodToMarkAttendance.bodyText2(context).custom(color: AppColor.lightTextColor(context)), + InfoTextLabelWidget(label: context.translation.selectMethodToMarkAttendance), 12.height, GridView.builder( padding: const EdgeInsets.all(0), @@ -910,4 +902,3 @@ class _SwipeTypeItem { required this.isEnabled, }); } - diff --git a/lib/views/full_screen_image_viewer.dart b/lib/views/full_screen_image_viewer.dart index 43aee198..9c463e06 100644 --- a/lib/views/full_screen_image_viewer.dart +++ b/lib/views/full_screen_image_viewer.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; +import '../new_views/app_style/app_color.dart'; + class FullScreenImageViewer extends StatelessWidget { final Widget child; @@ -12,7 +14,23 @@ class FullScreenImageViewer extends StatelessWidget { body: Stack( children: [ InteractiveViewer(clipBehavior: Clip.none, panEnabled: true, minScale: 0.5, maxScale: 4.0, child: child).center, - Positioned(top: MediaQuery.of(context).padding.top, left: 12.0, child: const Icon(Icons.clear).toShadowContainer(context, width: 40, padding: 8).onPress(() => Navigator.maybePop(context))), + SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FloatingActionButton( + heroTag: "full-screen-image-viewer", + backgroundColor: AppColor.background(context), + onPressed: () => Navigator.maybePop(context), + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder( + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14.0), + ), + child: Icon(Icons.clear, size: 24, color: AppColor.icon2Color(context)), + ), + ), + ), ], ), ); diff --git a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart index b157df2e..5d01acce 100644 --- a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart +++ b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart @@ -59,11 +59,12 @@ class _WoAutoCompleteFieldState extends State { @override Widget build(BuildContext context) { - final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(16)); return Container( decoration: BoxDecoration( color: AppColor.background(context), - borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], ), child: Autocomplete( @@ -93,7 +94,7 @@ class _WoAutoCompleteFieldState extends State { return TextField( controller: _controller, focusNode: fieldFocusNode, - style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 14, color: AppColor.textStyleColor(context)), textAlign: TextAlign.start, decoration: InputDecoration( border: border, @@ -101,16 +102,16 @@ class _WoAutoCompleteFieldState extends State { focusedBorder: border, enabledBorder: border, errorBorder: border, - contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + contentPadding: EdgeInsets.symmetric(vertical: 12.toScreenHeight, horizontal: 16.toScreenWidth), constraints: const BoxConstraints(), suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8), filled: true, enabled: widget.enabled, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(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), + floatingLabelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.floatingTextStyleColor(context)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.labelTextStyleColor(context)), labelText: "CM Work Order Number", - 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, diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index a876ffeb..6e2266ca 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -279,10 +279,7 @@ class _AssetDetailPageState extends State with SingleTickerProv children: [ InfoHeader16Widget(assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), if (context.userProvider.isEngineer && !(assetModel?.hasTrainingImageData ?? true)) - Text( - '*Upload Images for this asset', - style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), - ), + const InfoTextLabelWidget(label: '*Upload Images for this asset', color: AppColor.red30), ], ), ), diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index 43fbbd6d..b3c971e2 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -133,6 +133,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt IconButton( icon: Icon(!showListView ? Icons.view_list_sharp : Icons.grid_view_outlined, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + iconSize: 20, visualDensity: VisualDensity.compact, tooltip: !showListView ? "View as List" : "View as Grid", onPressed: () => setState(() { @@ -173,7 +174,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt }, ), ], - ).paddingOnly(start: 16, end: 16), + ).paddingOnly(start: 16, end: 8), ), body: SafeArea( child: LoadingManager( @@ -313,7 +314,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt floatingActionButton: FloatingActionButton( heroTag: "some tag 2", backgroundColor: AppColor.primary10, - child: "qr".toSvgAsset(height: 32, fit: BoxFit.fitHeight, color: AppColor.background(context)), + child: Icon(Icons.qr_code_2_rounded, size: 40, color: AppColor.addItemButtonColor(context)), onPressed: () async { String? result = await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const ScanQr()), diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index b3a2b252..cc589cbf 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.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/new_views/app_style/app_color.dart'; @@ -34,16 +36,16 @@ class InfoTextWidget extends StatelessWidget { "arrow_right".toSvgAsset(width: 17, color: AppColor.primary10), ], ); - } else if (copyValue) { - // text = Row( - // mainAxisSize: MainAxisSize.min, - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // Flexible(child: text), - // const SizedBox(width: 4), - // Icon(Icons.copy_rounded, color: AppColor.loadingColor(context), size: 12), - // ], - // ); + } else if (copyValue && (value?.isNotEmpty ?? false)) { + text = Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible(child: text), + 8.width, + const Icon(Icons.copy_rounded, color: AppColor.primary10, size: 14).onPress(() => Clipboard.setData(ClipboardData(text: value!))), + ], + ); } return text; } @@ -52,13 +54,14 @@ class InfoTextWidget extends StatelessWidget { class InfoTextLabelWidget extends StatelessWidget { final String label; final Color? color; + final TextStyle? textStyle; - InfoTextLabelWidget({required this.label, this.color, Key? key}) : super(key: key); + const InfoTextLabelWidget({required this.label, this.color, this.textStyle, Key? key}) : super(key: key); @override Widget build(BuildContext context) { Widget text; - text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: color ?? AppColor.lightTextColor(context))); + text = Text(label, style: (textStyle ?? AppTextStyles.bodyText2).copyWith(color: color ?? AppColor.lightTextColor(context))); return text; } } diff --git a/lib/views/widgets/qr/scan_qr.dart b/lib/views/widgets/qr/scan_qr.dart index 78c22e46..bdf46b6e 100644 --- a/lib/views/widgets/qr/scan_qr.dart +++ b/lib/views/widgets/qr/scan_qr.dart @@ -155,10 +155,16 @@ class _ScannerOverlay extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: FloatingActionButton( - heroTag: "some tag 3", + heroTag: "qr-scan", backgroundColor: AppColor.background(context), onPressed: onBack, - child: Icon(Icons.arrow_back, size: 24.toScreenWidth, color: AppColor.icon2Color(context)), + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder( + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14.0), + ), + child: Icon(Icons.arrow_back, size: 24, color: AppColor.icon2Color(context)), ), ), ), From a665a22925cca6d417fa3c7ed51b82c3d8d7717d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 15:47:14 +0300 Subject: [PATCH 56/73] improvements --- lib/views/widgets/images/multi_image_picker.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index a38bc0d5..a0533210 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -327,9 +327,10 @@ class _AttachmentPickerState extends State with SingleTickerPr widget.onChange!(widget.attachment); } setState(() {}); - return; } + return; } + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 50); if (pickedFile != null) { @@ -354,6 +355,6 @@ class _AttachmentPickerState extends State with SingleTickerPr setState(() {}); } - setState(() {}); + // setState(() {}); } } From 0b75903242aee7a7e6f2cc3c5f509211c231980f Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 15:53:33 +0300 Subject: [PATCH 57/73] improvements --- lib/new_views/common_widgets/custom_app_bar.dart | 4 +++- lib/new_views/common_widgets/default_app_bar.dart | 4 +++- lib/views/widgets/equipment/my_assets_page.dart | 4 +++- lib/views/widgets/qr/scan_qr.dart | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/new_views/common_widgets/custom_app_bar.dart b/lib/new_views/common_widgets/custom_app_bar.dart index 9ab589d1..edfda750 100644 --- a/lib/new_views/common_widgets/custom_app_bar.dart +++ b/lib/new_views/common_widgets/custom_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -20,7 +22,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { titleSpacing: 16, title: Row( children: [ - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { Navigator.of(context).pop(); }), Text( diff --git a/lib/new_views/common_widgets/default_app_bar.dart b/lib/new_views/common_widgets/default_app_bar.dart index 58ac9631..75732d47 100644 --- a/lib/new_views/common_widgets/default_app_bar.dart +++ b/lib/new_views/common_widgets/default_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -44,7 +46,7 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { title: Row( children: [ if (showBackButton) - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { if (onWillPopScope != null) { showDialog( context: context, diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index b3c971e2..4ad1f95c 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -97,7 +99,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt mainAxisAlignment: MainAxisAlignment.center, children: [ if (!widget.fromBottomBar) - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { Navigator.of(context).pop(); }), Container( diff --git a/lib/views/widgets/qr/scan_qr.dart b/lib/views/widgets/qr/scan_qr.dart index bdf46b6e..81fc6dfa 100644 --- a/lib/views/widgets/qr/scan_qr.dart +++ b/lib/views/widgets/qr/scan_qr.dart @@ -164,7 +164,7 @@ class _ScannerOverlay extends StatelessWidget { side: BorderSide(color: AppColor.borderColor(context)), borderRadius: BorderRadius.circular(14.0), ), - child: Icon(Icons.arrow_back, size: 24, color: AppColor.icon2Color(context)), + child: Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back, size: 24, color: AppColor.icon2Color(context)), ), ), ), From 4a39569c9eca805f15f2e20562e315c951423a37 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 16:01:54 +0300 Subject: [PATCH 58/73] improvements --- lib/modules/cm_module/cm_detail_page.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index 1bc46d8a..38ccaebe 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -122,6 +122,8 @@ class _CMDetailPageState extends State { if (isNurse) IconButton( icon: 'qr'.toSvgAsset(height: 24, width: 24), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, onPressed: () { ServiceRequestBottomSheet.getQRCodeBottomSheet(context: context); }, @@ -173,11 +175,16 @@ class _CMDetailPageState extends State { IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: _handlePrint, ), IconButton( icon: const Icon(Icons.home), + padding: EdgeInsets.zero, + iconSize: 20, + visualDensity: VisualDensity.compact, + tooltip:"Home", onPressed: () { // stopTimer(); Navigator.pop(context); From bec088dd5a55605281cc16a12c94e5bd185e5bbc Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 17:20:26 +0300 Subject: [PATCH 59/73] request number copy added. --- lib/modules/incident_module/incident_detail_page.dart | 2 +- .../equipment_internal_audit_detail_page.dart | 4 ++-- lib/modules/loan_module/pages/loan_equipment_detail_page.dart | 2 +- .../medical_gas_inspection_detail_page.dart | 2 +- lib/modules/pm_module/ppm_wo/ppm_details_page.dart | 2 +- .../tm_module/device_transfer/device_transfer_details.dart | 2 +- lib/modules/tm_module/gas_refill/gas_refill_details.dart | 2 +- lib/modules/traf_module/traf_request_detail_page.dart | 2 +- lib/views/widgets/equipment/asset_detail_page.dart | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index bba8588a..4f9ade6e 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -81,7 +81,7 @@ class IncidentDetailPage extends StatelessWidget { List requestDetails(BuildContext context, IncidentDataModel incidentData) { return [ InfoHeader16Widget("Request Details"), - InfoTextWidget(label: 'Incident No', value: incidentData.reqCode ?? "-"), + InfoTextWidget(label: 'Incident No', value: incidentData.reqCode ?? "-", copyValue: true), InfoTextWidget(label: 'Incident Type', value: incidentData.incidentClassificationName ?? "-"), InfoTextWidget(label: 'Incident Title', value: incidentData.incidentTitle ?? "-"), InfoTextWidget(label: 'Incident Description', value: incidentData.incidentDescription ?? "-"), 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 index f1f44a77..b617db05 100644 --- 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 @@ -185,7 +185,7 @@ class _EquipmentInternalAuditDetailPageState extends State { return [ InfoHeader16Widget("Request Details"), InfoTextWidget(label: 'Loan Type', value: loanData.loanTypeName ?? "-"), - InfoTextWidget(label: 'Loan No', value: loanData.reqCode ?? "-"), + InfoTextWidget(label: 'Loan No', value: loanData.reqCode ?? "-", copyValue: true), InfoTextWidget(label: 'Loan Period', value: loanData.loanPeriodName ?? "-"), InfoTextWidget(label: context.translation.site, value: loanData.siteName ?? "-"), InfoTextWidget(label: context.translation.department, value: loanData.departmentName ?? "-"), diff --git a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart index 7e076c8f..b499f20b 100644 --- a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart +++ b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart @@ -90,7 +90,7 @@ class _MedicalGasInspectionDetailPageState extends State { InfoHeader16Widget(planPreventiveVisit.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? ''), InfoTextWidget(label: context.translation.assetNumber, value: planPreventiveVisit.asset?.assetNumber ?? '-'), InfoTextWidget(label: context.translation.assetSN, value: planPreventiveVisit.asset?.assetSerialNo ?? '-'), - InfoTextWidget(label: context.translation.requestNo, value: planPreventiveVisit.visitNo ?? '-'), + InfoTextWidget(label: context.translation.requestNo, value: planPreventiveVisit.visitNo ?? '-', copyValue: true), if (planPreventiveVisit.acutalDateOfVisit != null) ...[ const Divider().defaultStyle(context), InfoTextWidget(label: context.translation.actualDate, value: planPreventiveVisit.acutalDateOfVisit?.toIso8601String().toAssetDetailsFormat ?? ""), diff --git a/lib/modules/tm_module/device_transfer/device_transfer_details.dart b/lib/modules/tm_module/device_transfer/device_transfer_details.dart index a7579fb2..3c4c0993 100644 --- a/lib/modules/tm_module/device_transfer/device_transfer_details.dart +++ b/lib/modules/tm_module/device_transfer/device_transfer_details.dart @@ -94,7 +94,7 @@ class _DeviceTransferDetailsState extends State { ), 8.height, InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.trNumber, value: _model?.transferCode ?? "-"), + InfoTextWidget(label: context.translation.trNumber, value: _model?.transferCode ?? "-", copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: _model?.name ?? "-"), const Divider().defaultStyle(context), InfoHeader16Widget(context.translation.assetInformation), diff --git a/lib/modules/tm_module/gas_refill/gas_refill_details.dart b/lib/modules/tm_module/gas_refill/gas_refill_details.dart index 520d2518..bbeee78a 100644 --- a/lib/modules/tm_module/gas_refill/gas_refill_details.dart +++ b/lib/modules/tm_module/gas_refill/gas_refill_details.dart @@ -204,7 +204,7 @@ class _GasRefillDetailsPageState extends State { ), 8.height, InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.requestNo, value: gasRefillModel.gasRefillNo ?? '-'), + InfoTextWidget(label: context.translation.requestNo, value: gasRefillModel.gasRefillNo ?? '-', copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: gasRefillModel.name ?? '-'), InfoTextWidget(label: context.translation.gasType, value: gasRefillModel.gasRefillDetails?[0].gasType?.name ?? '-'), InfoTextWidget(label: context.translation.cylinderType, value: gasRefillModel.gasRefillDetails?[0].cylinderType?.name ?? '-'), diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 31fcf68e..00673a35 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -119,7 +119,7 @@ class _TrafRequestDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoHeader16Widget("Request Information"), - InfoTextWidget(label: 'Request No', value: data.reqCode ?? '-'), + InfoTextWidget(label: 'Request No', value: data.reqCode ?? '-', copyValue: true), InfoTextWidget(label: 'Request type', value: data.requestTypeName ?? '-'), if (data.requestTypeValue == 1) ...[ 4.height, diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 6e2266ca..f9b7c294 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -297,7 +297,7 @@ class _AssetDetailPageState extends State with SingleTickerProv Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true), + InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true,copyValue: true), InfoTextWidget(label: context.translation.modelName, value: assetModel!.modelDefinition!.modelName, showEmptyValue: true), InfoTextWidget(label: context.translation.supplier, value: assetModel!.supplier?.suppliername, showEmptyValue: true), InfoTextWidget(label: context.translation.manufacture, value: assetModel!.modelDefinition!.manufacturerName, showEmptyValue: true), From 95d0bb4059c5af9fa0191d0f825670a167976e26 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 20 Jul 2026 11:04:52 +0300 Subject: [PATCH 60/73] demo request ui ux improvement --- .../demo_module/create_demo_request_page.dart | 165 ++++++++---------- .../demo_module/demo_detail_view_page.dart | 67 ++++--- 2 files changed, 103 insertions(+), 129 deletions(-) diff --git a/lib/modules/demo_module/create_demo_request_page.dart b/lib/modules/demo_module/create_demo_request_page.dart index a921ed91..4ff8f3ff 100644 --- a/lib/modules/demo_module/create_demo_request_page.dart +++ b/lib/modules/demo_module/create_demo_request_page.dart @@ -43,6 +43,8 @@ import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/work_order/vendor_provider.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import '../../../../../../new_views/common_widgets/default_app_bar.dart'; import '../../models/new_models/department.dart'; import 'demo_detail_view_page.dart'; @@ -92,76 +94,23 @@ class _CreateDemoRequestPageState extends State with Tick SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( + spacing: 12, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // SingleItemDropDownMenu( - // context: context, - // title: "Demo Period", - // initialValue: demoPeriodLookup, - // showShadow: false, - // validator: (value) { - // if (value == null) return "Mandatory"; - // return null; - // }, - // backgroundColor: AppColor.fieldBgColor(context), - // showAsBottomSheet: true, - // onSelect: (value) { - // demoPeriodLookup = value; - // setState(() {}); - // }, - // ), - // 16.height, ...siteSection(), - 16.height, ...doctorInfoSection(), - 16.height, ...assetSection(), - 16.height, ...vendorDetailsSection(), - 16.height, - "Attachments".bodyText(context).custom(color: AppColor.black10), - 8.height, - SingleItemDropDownMenu( - context: context, - height: 56.toScreenHeight, - title: "Document Type", - initialValue: demoDocumentLookup, - showShadow: false, - validator: (value) { - if (value == null) return "Mandatory"; - return null; - }, - backgroundColor: AppColor.fieldBgColor(context), - showAsBottomSheet: true, - onSelect: (value) { - demoDocumentLookup = value; - setState(() {}); - }, - ), - 16.height, - AttachmentPicker( - label: context.translation.attachments, - attachment: _attachments, - buttonColor: AppColor.black10.withOpacity(demoDocumentLookup != null ? 1 : .4), - onlyImages: false, - enabled: demoDocumentLookup != null, - documentType: demoDocumentLookup, - showAsListView: true, - buttonIcon: 'image-plus'.toSvgAsset(color: (AppColor.primary10).withOpacity(demoDocumentLookup != null ? 1 : .4)), - onChange: (attachments) { - _attachments = attachments; - setState(() {}); - }, - ), - 16.height, + ...attachmentSection(), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Checkbox( value: _demoFormModel.demoEaluationAcknowledge, - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), + visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { setState(() { @@ -172,24 +121,29 @@ class _CreateDemoRequestPageState extends State with Tick Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ - "DEMO outcome evaluation form acknowledgement".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), - 4.height, - "I acknowledge at the end of the trial period I will complete and submit the Medical Technology Trial Outcome Form within two weeks." - .tinyFont(context) - .custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral120), + InfoTextLabelWidget( + label: "DEMO outcome evaluation form acknowledgement", + textStyle: AppTextStyles.bodyText, + color: AppColor.textColor(context), + ), + InfoTextLabelWidget( + label: "I acknowledge at the end of the trial period I will complete and submit the Medical Technology Trial Outcome Form within two weeks.", + color: AppColor.lightTextColor(context), + ), ], ).expanded, ], ), - 16.height, Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Checkbox( value: _demoFormModel.acknowledgeNotEquipment, - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), + visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { setState(() { @@ -200,18 +154,23 @@ class _CreateDemoRequestPageState extends State with Tick Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ - "Acknowledgement of not using the equipment".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), - 4.height, - "I acknowledge at the end of the trial period I will not use the DEMO medical equipment & I will not book any patient on the DEMO medical equipment." - .tinyFont(context) - .custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral120), + InfoTextLabelWidget( + label: "Acknowledgement of not using the equipment", + textStyle: AppTextStyles.bodyText, + color: AppColor.textColor(context), + ), + InfoTextLabelWidget( + label: "I acknowledge at the end of the trial period I will not use the DEMO medical equipment & I will not book any patient on the DEMO medical equipment.", + color: AppColor.lightTextColor(context), + ), ], ).expanded, ], ), ], - ).toShadowContainer(context, borderRadius: 20), + ).toShadowContainer(context), ).expanded, FooterActionButton.footerContainer( context: context, @@ -231,8 +190,7 @@ class _CreateDemoRequestPageState extends State with Tick List siteSection() { return [ - 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Site Details'.addTranslation), SingleItemDropDownMenu( context: context, title: context.translation.site, @@ -252,7 +210,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.building, @@ -273,7 +230,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.floor, @@ -293,7 +249,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.department, @@ -318,8 +273,7 @@ class _CreateDemoRequestPageState extends State with Tick List doctorInfoSection() { return [ - "Doctor Details".bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Doctor Details'.addTranslation), AppTextFormField( initialValue: "", labelText: "Name", @@ -336,7 +290,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docName = value; }, ), - 8.height, AppTextFormField( initialValue: "", labelText: "Contact Number", @@ -353,7 +306,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docNumber = value; }, ), - 8.height, AppTextFormField( initialValue: "", labelText: "Email", @@ -373,7 +325,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docEmail = value; }, ), - 8.height, AppTextFormField( labelText: "Item Description", validator: (value) { @@ -391,7 +342,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.itemDescription = value; }, ), - 8.height, AppTextFormField( labelText: "Request Description", // validator: (value) { @@ -414,8 +364,7 @@ class _CreateDemoRequestPageState extends State with Tick List assetSection() { return [ - 'Asset Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Asset Details'.addTranslation), AppTextFormField( labelText: "Asset Name", validator: (value) { @@ -433,7 +382,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.assetName = value; }, ), - 8.height, AppTextFormField( labelText: "Model", validator: (value) { @@ -451,7 +399,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.model = value; }, ), - 8.height, AppTextFormField( labelText: "Manufacturer", validator: (value) { @@ -469,7 +416,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.manufacturer = value; }, ), - 8.height, SingleItemDropDownMenu( context: context, height: 56.toScreenHeight, @@ -492,8 +438,7 @@ class _CreateDemoRequestPageState extends State with Tick List vendorDetailsSection() { return [ - 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Vendor Details'.addTranslation), AppTextFormField( labelText: 'Vendor Name'.addTranslation, style: Theme.of(context).textTheme.titleMedium, @@ -506,7 +451,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorName = value; }, ), - 8.height, AppTextFormField( labelText: 'Representative Name'.addTranslation, style: Theme.of(context).textTheme.titleMedium, @@ -519,7 +463,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorRepresentativeName = value; }, ), - 8.height, AppTextFormField( // controller: vendorPhoneController, labelText: "Contact Number", @@ -533,7 +476,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorContactNumber = value; }, ), - 8.height, AppTextFormField( labelText: "Email", // controller: vendorEmailController, @@ -550,6 +492,43 @@ class _CreateDemoRequestPageState extends State with Tick ]; } + List attachmentSection() { + return [ + InfoHeader16Widget('Attachments'.addTranslation), + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Document Type", + initialValue: demoDocumentLookup, + showShadow: false, + validator: (value) { + if (value == null) return "Mandatory"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + demoDocumentLookup = value; + setState(() {}); + }, + ), + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10.withOpacity(demoDocumentLookup != null ? 1 : .4), + onlyImages: false, + enabled: demoDocumentLookup != null, + documentType: demoDocumentLookup, + showAsListView: true, + buttonIcon: 'image-plus'.toSvgAsset(color: (AppColor.primary10).withOpacity(demoDocumentLookup != null ? 1 : .4)), + onChange: (attachments) { + _attachments = attachments; + setState(() {}); + }, + ), + ]; + } + Future _submit() async { FocusScope.of(context).unfocus(); if (_formKey.currentState!.validate()) { diff --git a/lib/modules/demo_module/demo_detail_view_page.dart b/lib/modules/demo_module/demo_detail_view_page.dart index cac6e356..6d98e438 100644 --- a/lib/modules/demo_module/demo_detail_view_page.dart +++ b/lib/modules/demo_module/demo_detail_view_page.dart @@ -16,6 +16,8 @@ import 'package:test_sa/modules/loan_module/models/loan_attachment_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/swipe_module/dialoge/acknowledge_work_dialog.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import '../../controllers/api_routes/urls.dart'; @@ -111,7 +113,6 @@ class _DemoDetailViewPageState extends State { ), 8.height, ], - 12.height, ...requesterDetails(context, demoData), const Divider().defaultStyle(context), ...requestDetails(context, demoData), @@ -123,10 +124,7 @@ class _DemoDetailViewPageState extends State { ...vendorDetails(context, demoData), if (allAttachments.isNotEmpty) ...[ const Divider().defaultStyle(context), - Text( - "Attachments".addTranslation, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), + InfoHeader16Widget("Attachments"), FilesList( showAsListView: true, images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList(), @@ -134,10 +132,7 @@ class _DemoDetailViewPageState extends State { ], if (suppAttachments.isNotEmpty) ...[ const Divider().defaultStyle(context), - Text( - "Supplier Attachments".addTranslation, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), + InfoHeader16Widget("Supplier Attachments"), FilesList( showAsListView: true, images: suppAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList(), @@ -191,53 +186,53 @@ class _DemoDetailViewPageState extends State { List requestDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Request Details", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Request No: ${model?.requestNo ?? "-"}'.bodyText(context), - 'Demo No: ${model?.demoNo ?? "-"}'.bodyText(context), - 'Demo Period: ${model?.demoPeriod?.name ?? "-"}'.bodyText(context), - '${context.translation.site}: ${model?.site?.custName ?? "-"}'.bodyText(context), - '${context.translation.building}: ${model?.building?.name ?? "-"}'.bodyText(context), - '${context.translation.floor}: ${model?.floor?.name ?? "-"}'.bodyText(context), - '${context.translation.department}: ${model?.department?.departmentName ?? "-"}'.bodyText(context), - 'Item Description: ${model?.itemDescription ?? "-"}'.bodyText(context), - 'Request Description: ${model?.requestDescription ?? "-"}'.bodyText(context), + InfoHeader16Widget("Request Information"), + InfoTextWidget(label: "Request No", value: model?.requestNo ?? "-", copyValue: true), + InfoTextWidget(label: "Demo No", value: model?.demoNo ?? "-"), + InfoTextWidget(label: "Demo Period", value: model?.demoPeriod?.name ?? "-"), + InfoTextWidget(label: context.translation.site, value: model?.site?.custName ?? "-"), + InfoTextWidget(label: context.translation.building, value: model?.building?.name ?? "-"), + InfoTextWidget(label: context.translation.floor, value: model?.floor?.name ?? "-"), + InfoTextWidget(label: context.translation.department, value: model?.department?.departmentName ?? "-"), + InfoTextWidget(label: "Item Description", value: model?.itemDescription ?? "-"), + InfoTextWidget(label: "Request Description", value: model?.requestDescription ?? "-"), ]; } List vendorDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Vendor Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.supplier?.name ?? model?.vendorName ?? "-"}'.bodyText(context), - 'Representative Name: ${model?.suppPerson?.name ?? model?.vendorRepresentativeName ?? "-"}'.bodyText(context), - 'Contact: ${model?.supplier?.contact ?? model?.vendorContactNumber ?? "-"}'.bodyText(context), - 'Email: ${model?.supplier?.email ?? model?.vendorEmail ?? "-"}'.bodyText(context), + InfoHeader16Widget("Vendor Information"), + InfoTextWidget(label: "Name", value: model?.supplier?.name ?? model?.vendorName ?? "-"), + InfoTextWidget(label: "Representative Name", value: model?.suppPerson?.name ?? model?.vendorRepresentativeName ?? "-"), + InfoTextWidget(label: "Contact", value: model?.supplier?.contact ?? model?.vendorContactNumber ?? "-"), + InfoTextWidget(label: "Email", value: model?.supplier?.email ?? model?.vendorEmail ?? "-"), ]; } List assetDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Asset Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - '${context.translation.assetName}: ${model?.assetName ?? "-"}'.bodyText(context), - '${context.translation.manufacture}: ${model?.manufacturer ?? "-"}'.bodyText(context), - '${context.translation.model}: ${model?.model ?? "-"}'.bodyText(context), + InfoHeader16Widget("Asset Information"), + InfoTextWidget(label: context.translation.assetName, value: model?.assetName ?? "-"), + InfoTextWidget(label: context.translation.manufacture, value: model?.manufacturer ?? "-"), + InfoTextWidget(label: context.translation.model, value: model?.model ?? "-"), ]; } List doctorDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Doctor Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.doctorName ?? "-"}'.bodyText(context), - 'Contact: ${model?.doctorContactNumber ?? "-"}'.bodyText(context), - 'Email: ${model?.doctorContactEmail ?? "-"}'.bodyText(context), + InfoHeader16Widget("Doctor Information"), + InfoTextWidget(label: "Name", value: model?.doctorName ?? "-"), + InfoTextWidget(label: "Contact", value: model?.doctorContactNumber ?? "-"), + InfoTextWidget(label: "Email", value: model?.doctorContactEmail ?? "-"), ]; } List requesterDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Requester Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.requesterUser?.requesterName ?? "-"}'.bodyText(context), - 'Email: ${model?.requesterUser?.requesterEmail ?? "-"}'.bodyText(context), - 'Contact: ${model?.requesterUser?.requesterMobile ?? "-"}'.bodyText(context), + InfoHeader16Widget("Requester Information"), + InfoTextWidget(label: "Name", value: model?.requesterUser?.requesterName ?? "-"), + InfoTextWidget(label: "Email", value: model?.requesterUser?.requesterEmail ?? "-"), + InfoTextWidget(label: "Contact", value: model?.requesterUser?.requesterMobile ?? "-"), // 'Extension: ${loanData.requesterExtensionNo ?? "-"}'.bodyText(context), ]; } From 9130ade21d44bf3e20a3529d92bb30312536bdf7 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 20 Jul 2026 11:10:35 +0300 Subject: [PATCH 61/73] improvement --- lib/views/update_available_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/update_available_screen.dart b/lib/views/update_available_screen.dart index 558a0233..ff71f215 100644 --- a/lib/views/update_available_screen.dart +++ b/lib/views/update_available_screen.dart @@ -25,13 +25,13 @@ class UpdateAvailableScreen extends StatelessWidget { 48.height, Text( "Update Available", - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)), ), 24.height, Text( "A new update is available on store.\nUpdate now to enjoy the latest features, improvements, and bug fixes for a smoother experience! ", textAlign: TextAlign.center, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), + style: AppTextStyles.heading6.copyWith(color: AppColor.lightTextColor(context)), ), 24.height, AppFilledButton( From cb1a1475fa5e50f2fb5ce014be5d76a6ab8f626d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 20 Jul 2026 15:09:00 +0300 Subject: [PATCH 62/73] pagination api calling bug fixed. --- lib/dashboard_latest/dashboard_view.dart | 86 ++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/lib/dashboard_latest/dashboard_view.dart b/lib/dashboard_latest/dashboard_view.dart index ca986cc1..6de6e88b 100644 --- a/lib/dashboard_latest/dashboard_view.dart +++ b/lib/dashboard_latest/dashboard_view.dart @@ -12,6 +12,7 @@ import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; +import 'package:test_sa/extensions/enum_extensions.dart'; import 'package:test_sa/dashboard_latest/widgets/progress_fragment.dart'; import 'package:test_sa/dashboard_latest/widgets/request_category_fragment_housekeeper.dart'; import 'package:test_sa/dashboard_latest/widgets/requests_fragment.dart'; @@ -103,15 +104,86 @@ class _DashboardViewState extends State { void handleScroll() async { _scrollController = ScrollController(); _scrollController.addListener(() async { - if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_dashBoardProvider.isDetailLoading) { - _dashBoardProvider.pageNum = _dashBoardProvider.pageNum + 1; - if (_dashBoardProvider.currentListIndex == null) { - } else { - _dashBoardProvider.getRequestDetail( + if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { + final allRequestsProvider = Provider.of(context, listen: false); + + // Check which scenario is active and call appropriate pagination API + if (_dashBoardProvider.listIndex.isEmpty) { + // No category selected - paginate AllRequests + if (!allRequestsProvider.isAllLoading && allRequestsProvider.nextPage) { + allRequestsProvider.getAllRequests(context); + } + } else if (_dashBoardProvider.listIndex.length == 1 && _dashBoardProvider.isUnassignedTab(_dashBoardProvider.listIndex[0])) { + // Only UnAssigned tab selected - paginate with getRequestDetail + if (!_dashBoardProvider.isDetailLoading && _dashBoardProvider.nextPage) { + _dashBoardProvider.pageNum = _dashBoardProvider.pageNum + 1; + _dashBoardProvider.getRequestDetail( usersType: user.type!, showLoader: false, - status: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].tag, - tabId: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].id); + status: _dashBoardProvider.tabs[_dashBoardProvider.listIndex[0]].tag, + tabId: _dashBoardProvider.tabs[_dashBoardProvider.listIndex[0]].id, + ); + } + } else { + // Multi-select categories - paginate with getDashboardMultifilterRequests + if (!allRequestsProvider.isMultiFilterLoading && allRequestsProvider.nextPage) { + // Build filter arrays based on selected tabs + List statusTransaction = []; + List priority = []; + List displayData = []; + + for (int index in _dashBoardProvider.listIndex) { + if (index >= 0 && index < _dashBoardProvider.tabs.length) { + CategoryTabs tab = _dashBoardProvider.tabs[index]; + + // High Priority + if (tab.isHighPriority) { + if (!priority.contains(1)) priority.add(1); + } + + // Overdue + if (tab.isOverDue) { + if (!displayData.contains(1)) displayData.add(1); + } + + // Status categories + if (!tab.isHighPriority && !tab.isOverDue && tab.requestType != RequestTypeEnum.unassigned) { + switch (tab.requestType) { + case RequestTypeEnum.open: + if (!statusTransaction.contains(1)) statusTransaction.add(1); + break; + case RequestTypeEnum.inProgress: + if (!statusTransaction.contains(2)) statusTransaction.add(2); + break; + case RequestTypeEnum.completed: + if (!statusTransaction.contains(4)) statusTransaction.add(4); + break; + case RequestTypeEnum.closed: + if (!statusTransaction.contains(4)) statusTransaction.add(4); + break; + case RequestTypeEnum.canceled: + if (!statusTransaction.contains(5)) statusTransaction.add(5); + break; + case RequestTypeEnum.pendingAcknowledgment: + if (!statusTransaction.contains(3)) statusTransaction.add(3); + break; + case RequestTypeEnum.unassigned: + case RequestTypeEnum.highPriority: + case RequestTypeEnum.overdue: + break; + } + } + } + } + + allRequestsProvider.getDashboardMultifilterRequests( + context, + pagination: false, + statusTransaction: statusTransaction, + priority: priority, + displayData: displayData, + ); + } } } }); From 897a75e2c78b6b116bc05790843a7019a2acc140 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 21 Jul 2026 12:46:23 +0300 Subject: [PATCH 63/73] improvements --- lib/modules/cm_module/cm_detail_page.dart | 3 +- .../provider/export_provider.dart | 1 + .../widgets/file_success_dialog.dart | 41 +++----------- .../device_transfer/device_filter_screen.dart | 1 + .../device_transfer/device_search_screen.dart | 1 + .../common_widgets/default_app_bar.dart | 12 ++-- .../my_request/all_requests_filter_page.dart | 2 +- .../my_request/all_requests_search_page.dart | 1 + .../my_request/my_requests_page.dart | 4 +- .../widgets/equipment/my_assets_page.dart | 6 +- .../multiple_selection_fullscreen_dialog.dart | 6 +- .../selection_fullscreen_dialog.dart | 56 ++++++++++--------- 12 files changed, 61 insertions(+), 73 deletions(-) diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index 38ccaebe..7dd2ba42 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -121,9 +121,10 @@ class _CMDetailPageState extends State { }), if (isNurse) IconButton( - icon: 'qr'.toSvgAsset(height: 24, width: 24), + icon: 'qr'.toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, visualDensity: VisualDensity.compact, + tooltip: "QR Code", onPressed: () { ServiceRequestBottomSheet.getQRCodeBottomSheet(context: context); }, diff --git a/lib/modules/export_module/provider/export_provider.dart b/lib/modules/export_module/provider/export_provider.dart index f80c390e..81b80f8e 100644 --- a/lib/modules/export_module/provider/export_provider.dart +++ b/lib/modules/export_module/provider/export_provider.dart @@ -50,6 +50,7 @@ class ExportApiProvider extends ChangeNotifier { valueColor: textColor, elevation: 4, msgFontSize: 14, + borderRadius: 14, backgroundColor: backgroundColor, cancel: Cancel( cancelClicked: () { diff --git a/lib/modules/export_module/widgets/file_success_dialog.dart b/lib/modules/export_module/widgets/file_success_dialog.dart index 015ff7e0..e66c0c43 100644 --- a/lib/modules/export_module/widgets/file_success_dialog.dart +++ b/lib/modules/export_module/widgets/file_success_dialog.dart @@ -7,6 +7,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.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/views/widgets/item_views/info_header_widget.dart'; /// Reusable success dialog for file operations (export, print, etc.) class FileSuccessDialog { @@ -31,47 +32,25 @@ class FileSuccessDialog { return Dialog( backgroundColor: AppColor.background(context), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14), ), child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Close button Align( alignment: Alignment.topRight, - child: 'clear'.toSvgAsset( - color: AppColor.icon2Color(context), - width: 18, - height: 18, - ).onPress(() { + child: 'clear'.toSvgAsset(color: AppColor.icon2Color(context), width: 18, height: 18).onPress(() { Navigator.of(dialogContext).pop(); }), ), - // Success icon 'done'.toSvgAsset(color: AppColor.green70, width: 56, height: 56), const SizedBox(height: 24), - // Title - Text( - title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: AppColor.headingTextColor(context), - ), - textAlign: TextAlign.center, - ), + title.heading5(context).custom(fontWeight: FontWeight.w600, color: AppColor.textStyleColor(context)), const SizedBox(height: 12), - // Message - Text( - message, - style: TextStyle( - fontSize: 18, - color: AppColor.lightTextColor(context), - ), - textAlign: TextAlign.center, - ), + message.heading6(context).custom(align: TextAlign.center, fontWeight: FontWeight.w500, color: AppColor.lightTextColor(context)), const SizedBox(height: 32), // Action buttons Row( @@ -84,14 +63,13 @@ class FileSuccessDialog { final result = await OpenFile.open(filePath); if (result.type != ResultType.done) { log('⚠️ Could not open file: ${result.message}'); - 'No app found to open this file type. File saved to: $filePath' - .showToast; + 'No app found to open this file type. File saved to: $filePath'.showToast; } }, label: 'Open', // buttonColor: AppColor.primary10, showIcon: true, - icon: Icon(Icons.folder_open, color: AppColor.background(context)), + icon: Icon(Icons.folder_open, color: AppColor.background(context)), height: 48, ), ), @@ -131,4 +109,3 @@ class FileSuccessDialog { ); } } - diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index bf55ae1e..04802a5e 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -91,6 +91,7 @@ class _DeviceFilterScreenState extends State { return Scaffold( appBar: DefaultAppBar( title: context.translation.filter, + showBackButtonAsCancel: true, actions: [ if (loading == false && _showResetButton()) TextButton( diff --git a/lib/modules/tm_module/device_transfer/device_search_screen.dart b/lib/modules/tm_module/device_transfer/device_search_screen.dart index b5a90457..88050fb2 100644 --- a/lib/modules/tm_module/device_transfer/device_search_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_search_screen.dart @@ -61,6 +61,7 @@ class _DeviceSearchScreenState extends State { return Scaffold( appBar: DefaultAppBar( title: context.translation.search, + showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( diff --git a/lib/new_views/common_widgets/default_app_bar.dart b/lib/new_views/common_widgets/default_app_bar.dart index 75732d47..1f68b56a 100644 --- a/lib/new_views/common_widgets/default_app_bar.dart +++ b/lib/new_views/common_widgets/default_app_bar.dart @@ -18,8 +18,11 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { final bool showBackButton; final VoidCallback? onWillPopScope; final TextStyle? titleStyle; + final bool showBackButtonAsCancel; - const DefaultAppBar({this.title, this.onBackPress, this.onWillPopScope, this.actions, this.titleStyle, this.showHomeActionButton = false, this.showBackButton = true, Key? key}) : super(key: key); + const DefaultAppBar( + {this.title, this.onBackPress, this.onWillPopScope, this.actions, this.titleStyle, this.showHomeActionButton = false, this.showBackButton = true, this.showBackButtonAsCancel = false, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { @@ -46,7 +49,7 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { title: Row( children: [ if (showBackButton) - Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { + Icon(showBackButtonAsCancel ? Icons.clear : (Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back)).onPress(() { if (onWillPopScope != null) { showDialog( context: context, @@ -65,11 +68,10 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { Navigator.of(context).pop(); } }), - 10.width, + 12.width, Text( title ?? "", - style: titleStyle ?? - (context.isTablet() ? AppTextStyles.heading4 : AppTextStyles.heading3).copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + style: titleStyle ?? (AppTextStyles.heading5).copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ).expanded, ], ), 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 a90c8188..0c4c61ab 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 @@ -169,7 +169,7 @@ class _AllRequestsFilterPageState extends State { } return Scaffold( appBar: DefaultAppBar( - title: context.translation.filter, + title: context.translation.filter,showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( diff --git a/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart b/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart index 1dd6da60..b49834b4 100644 --- a/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart +++ b/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart @@ -88,6 +88,7 @@ class _AllRequestsSearchPageState extends State { // ).paddingOnly(bottom: 75), appBar: DefaultAppBar( title: context.translation.search, + showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( 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 ee227136..227d46f1 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 @@ -194,7 +194,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid ], ), ).onPress(() async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => AllRequestsSearchPage(data: _search))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => AllRequestsSearchPage(data: _search))); if (result != null) { _search = result; setState(() {}); @@ -227,7 +227,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => AllRequestsFilterPage(data: _search))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => AllRequestsFilterPage(data: _search))); if (result != null) { _search = result; setState(() {}); diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index 4ad1f95c..37df435b 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -99,7 +99,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt mainAxisAlignment: MainAxisAlignment.center, children: [ if (!widget.fromBottomBar) - Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { Navigator.of(context).pop(); }), Container( @@ -124,7 +124,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt ], ), ).onPress(() async { - final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const DeviceSearchScreen())); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => const DeviceSearchScreen())); if (result != null) { _searchAsset = result; } @@ -167,7 +167,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => DeviceFilterScreen(data: _searchAsset))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => DeviceFilterScreen(data: _searchAsset))); if (result != null) { _searchAsset = result; } diff --git a/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart b/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart index 5adeb69e..c07d2870 100644 --- a/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart +++ b/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart @@ -7,6 +7,7 @@ import 'package:test_sa/models/base.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.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'; typedef SelectionBuilderString = String Function(dynamic); @@ -61,8 +62,9 @@ class _SelectionBottomSheetState extends State extends State Date: Tue, 21 Jul 2026 12:48:50 +0300 Subject: [PATCH 64/73] improvements --- .../pm_module/recurrent_wo/components/task_info_widget.dart | 2 +- lib/modules/tm_module/tasks/task_request_detail_view.dart | 2 +- lib/views/widgets/equipment/asset_detail_page.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart index e76ca8bd..d65022c7 100644 --- a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart +++ b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart @@ -80,7 +80,7 @@ class _RecurrentTaskInfoWidgetState extends State { ), 8.height, InfoHeader16Widget(widget.model!.title!), - InfoTextWidget(label: context.translation.taskNo, value: widget.model!.taskNo!), + InfoTextWidget(label: context.translation.taskNo, value: widget.model!.taskNo!, copyValue: true), InfoTextWidget(label: 'Plan Name:'.addTranslation, value: widget.model?.planRecurrent?.planName), InfoTextWidget(label: context.translation.site, value: widget.model!.site!.siteName!), if (widget.isHouseKeeper) ...[ diff --git a/lib/modules/tm_module/tasks/task_request_detail_view.dart b/lib/modules/tm_module/tasks/task_request_detail_view.dart index 347dd114..326c3989 100644 --- a/lib/modules/tm_module/tasks/task_request_detail_view.dart +++ b/lib/modules/tm_module/tasks/task_request_detail_view.dart @@ -137,7 +137,7 @@ class _TaskRequestDetailsViewState extends State { const Divider().defaultStyle(context), InfoHeader16Widget("Task Details".addTranslation), InfoTextWidget(label: context.translation.taskType, value: taskModel?.taskType?.typeName ?? "-"), - InfoTextWidget(label: context.translation.taskNo, value: taskModel?.taskJobNo ?? "-"), + InfoTextWidget(label: context.translation.taskNo, value: taskModel?.taskJobNo ?? "-", copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: taskModel?.userCreated?.userName ?? "-"), if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == true) ...[ recallAlertTypeWidget(taskModel: taskProvider.taskRequestModel!), diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index f9b7c294..0a51cc87 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -297,7 +297,7 @@ class _AssetDetailPageState extends State with SingleTickerProv Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true,copyValue: true), + InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true, copyValue: true), InfoTextWidget(label: context.translation.modelName, value: assetModel!.modelDefinition!.modelName, showEmptyValue: true), InfoTextWidget(label: context.translation.supplier, value: assetModel!.supplier?.suppliername, showEmptyValue: true), InfoTextWidget(label: context.translation.manufacture, value: assetModel!.modelDefinition!.manufacturerName, showEmptyValue: true), From c922f9e4a26c3802fb43e50fb748cdf412b1e05b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 22 Jul 2026 10:19:25 +0300 Subject: [PATCH 65/73] improvements --- lib/extensions/context_extension.dart | 1 + .../swipe_module/dialoge/confirm_dialog.dart | 122 +++++++++--------- .../utils/swipe_general_utils.dart | 1 + lib/views/widgets/dialogs/confirm_dialog.dart | 14 +- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index a33130cb..8b2a4b23 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -7,6 +7,7 @@ import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/info_dialog.dart'; import 'package:test_sa/views/full_screen_image_viewer.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import '../controllers/providers/settings/setting_provider.dart'; import '../l10n/app_localizations.dart'; diff --git a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart index 60db0c03..e7c92bec 100644 --- a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart +++ b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart @@ -1,61 +1,61 @@ -import 'package:flutter/material.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/new_views/app_style/app_color.dart'; -import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; - -class ConfirmDialog extends StatelessWidget { - final String? title; - final String? message; - final String? okTitle; - final VoidCallback? onTap; - final VoidCallback? onCloseTap; - - const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - backgroundColor: AppColor.background(context), - shape: const RoundedRectangleBorder(), - insetPadding: const EdgeInsets.only(left: 21, right: 21), - child: Padding( - padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - title ?? "Confirm", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), - ).paddingOnly(top: 16), - ), - IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.close), - color: AppColor.iconColor(context), - constraints: const BoxConstraints(), - onPressed: () => onCloseTap ?? Navigator.pop(context), - // onPressed: () => Navigator.pop(context), - ) - ], - ), - message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), - 28.height, - AppFilledButton( - label: okTitle ?? "OK", - onPressed: onTap ?? () => Navigator.pop(context), - textColor: Colors.white, - //color: Ap.green, - ), - ], - ), - ), - ); - } -} +// import 'package:flutter/material.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/new_views/app_style/app_color.dart'; +// import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +// +// class ConfirmDialog extends StatelessWidget { +// final String? title; +// final String? message; +// final String? okTitle; +// final VoidCallback? onTap; +// final VoidCallback? onCloseTap; +// +// const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); +// +// @override +// Widget build(BuildContext context) { +// return Dialog( +// backgroundColor: AppColor.background(context), +// shape: const RoundedRectangleBorder(), +// insetPadding: const EdgeInsets.only(left: 21, right: 21), +// child: Padding( +// padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisSize: MainAxisSize.min, +// children: [ +// Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Expanded( +// child: Text( +// title ?? "Confirm", +// style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), +// ).paddingOnly(top: 16), +// ), +// IconButton( +// padding: EdgeInsets.zero, +// icon: const Icon(Icons.close), +// color: AppColor.iconColor(context), +// constraints: const BoxConstraints(), +// onPressed: () => onCloseTap ?? Navigator.pop(context), +// // onPressed: () => Navigator.pop(context), +// ) +// ], +// ), +// message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), +// 28.height, +// AppFilledButton( +// label: okTitle ?? "OK", +// onPressed: onTap ?? () => Navigator.pop(context), +// textColor: Colors.white, +// //color: Ap.green, +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/new_views/swipe_module/utils/swipe_general_utils.dart b/lib/new_views/swipe_module/utils/swipe_general_utils.dart index ac6c7c0f..9bace21f 100644 --- a/lib/new_views/swipe_module/utils/swipe_general_utils.dart +++ b/lib/new_views/swipe_module/utils/swipe_general_utils.dart @@ -29,6 +29,7 @@ import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; import 'package:test_sa/new_views/swipe_module/utils/location_utils.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/qr/scan_qr.dart'; diff --git a/lib/views/widgets/dialogs/confirm_dialog.dart b/lib/views/widgets/dialogs/confirm_dialog.dart index 6d8ef764..60db0c03 100644 --- a/lib/views/widgets/dialogs/confirm_dialog.dart +++ b/lib/views/widgets/dialogs/confirm_dialog.dart @@ -2,21 +2,22 @@ import 'package:flutter/material.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/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; class ConfirmDialog extends StatelessWidget { final String? title; - final String message; + final String? message; final String? okTitle; final VoidCallback? onTap; final VoidCallback? onCloseTap; - const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); @override Widget build(BuildContext context) { return Dialog( - backgroundColor: Colors.white, + backgroundColor: AppColor.background(context), shape: const RoundedRectangleBorder(), insetPadding: const EdgeInsets.only(left: 21, right: 21), child: Padding( @@ -31,21 +32,20 @@ class ConfirmDialog extends StatelessWidget { Expanded( child: Text( title ?? "Confirm", - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: Colors.black87, 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( padding: EdgeInsets.zero, icon: const Icon(Icons.close), - color: Colors.black87, + color: AppColor.iconColor(context), constraints: const BoxConstraints(), onPressed: () => onCloseTap ?? Navigator.pop(context), // onPressed: () => Navigator.pop(context), ) ], ), - 16.height, - message.bodyText(context), + message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), 28.height, AppFilledButton( label: okTitle ?? "OK", From 1c280a06c4ab4ba1916328638b5fb82e32ebe1f4 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 22 Jul 2026 10:55:16 +0300 Subject: [PATCH 66/73] v1.8.1+47 uploaded --- lib/controllers/api_routes/urls.dart | 6 +++--- lib/views/widgets/item_views/info_text_widget.dart | 11 ++++++++++- pubspec.yaml | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 68fde900..d532385f 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,11 +1,11 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "44"; + static const String appReleaseBuildNumber = "47"; - // static const host1 = "https://atomsm.hmg.com"; // production url + 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://atomsmuat.hmg.com"; // local UAT url static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index cc589cbf..29f3fba0 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -1,6 +1,9 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.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/new_views/app_style/app_color.dart'; @@ -43,7 +46,13 @@ class InfoTextWidget extends StatelessWidget { children: [ Flexible(child: text), 8.width, - const Icon(Icons.copy_rounded, color: AppColor.primary10, size: 14).onPress(() => Clipboard.setData(ClipboardData(text: value!))), + const Icon(Icons.copy_rounded, color: AppColor.primary10, size: 14).onPress(() { + Clipboard.setData(ClipboardData(text: value!)).then((value) { + if (Platform.isIOS) { + "Copied.".addTranslation.showToast; + } + }); + }), ], ); } diff --git a/pubspec.yaml b/pubspec.yaml index 12581b33..3d3b693d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.8+44 +version: 1.8.1+47 environment: sdk: ">=3.5.0 <4.0.0" From 9f3a5b234f0246686bba89981438ef1b25c6c60d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 26 Jul 2026 09:45:40 +0300 Subject: [PATCH 67/73] improvement. --- .../pages/land_page/contact_us_bottom_sheet.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/new_views/pages/land_page/contact_us_bottom_sheet.dart b/lib/new_views/pages/land_page/contact_us_bottom_sheet.dart index ecaac532..4574b7ac 100644 --- a/lib/new_views/pages/land_page/contact_us_bottom_sheet.dart +++ b/lib/new_views/pages/land_page/contact_us_bottom_sheet.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -77,7 +79,11 @@ class ContactUsBottomSheet extends StatelessWidget { iconSize: 20, tooltip: "Copy", onPressed: () { - Clipboard.setData(ClipboardData(text: sites[index].phoneNumber!)); + Clipboard.setData(ClipboardData(text: sites[index].phoneNumber!)).then((value) { + if (Platform.isIOS) { + "Copied.".addTranslation.showToast; + } + }); }, ), IconButton( From a1097341710b486ffe722b9c0a4a810079e13d42 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Jul 2026 09:50:30 +0300 Subject: [PATCH 68/73] cleaner add button added. --- ...request_category_fragment_housekeeper.dart | 22 ++++++++++--------- .../pages/cleaner_search_form_view.dart | 17 +++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart b/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart index 38f1e95a..6a7fd948 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment_housekeeper.dart @@ -32,6 +32,7 @@ class _RequestCategoryFragmentHouseKeeperState extends State _headerFadeAnimation; late Animation _headerSlideAnimation; final ScrollController _scrollController = ScrollController(); + // int _selectedIndex = 0; // Default first tab @override @@ -75,7 +76,6 @@ class _RequestCategoryFragmentHouseKeeperState extends State(context, listen: false); List tabs = CategoryTabs.getTabs(userType: UsersTypes.houseKeeper, context: context); @@ -122,14 +122,17 @@ class _RequestCategoryFragmentHouseKeeperState extends State_toggleSort(dashboardProvider)), - ) + 'add_icon'.toSvgAsset(width: 24, height: 24).onPress(() { + Navigator.of(context).pushNamed('/cleaner_search'); + }), + // Transform.flip( + // flipY: isAscending, + // child: 'dashboard/sort_descending'.toSvgAsset( + // width: 24, + // height: 24, + // color: AppColor.headingTextColor(context), + // ).onPress(()=>_toggleSort(dashboardProvider)), + // ) ], ).paddingOnly(start: 16, end: 16), ), @@ -237,4 +240,3 @@ class _RequestCategoryFragmentHouseKeeperState extends State { child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, + spacing: 12, children: [ // Task Type Dropdown SingleItemDropDownMenu( @@ -204,18 +204,17 @@ class _CleanerSearchFormViewState extends State { ), // Scan QR Code Button - 8.height, GestureDetector( onTap: selectedTaskType != null ? _onScanQRCode : null, child: Container( height: 50, alignment: Alignment.center, decoration: BoxDecoration( - color: selectedTaskType != null ? Colors.white : Colors.grey[300], - borderRadius: BorderRadius.circular(10), + color: selectedTaskType != null ? AppColor.background(context) : Colors.grey[300], + borderRadius: BorderRadius.circular(16), border: Border.all( - color: selectedTaskType != null ? AppColor.blueStatus(context) : Colors.grey, - width: 2, + color: selectedTaskType != null ? AppColor.borderColor(context) : Colors.grey, + width: 1, ), ), padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth, vertical: 8.toScreenHeight), @@ -225,16 +224,16 @@ class _CleanerSearchFormViewState extends State { "scan_asset".toSvgAsset( height: 22, fit: BoxFit.fitHeight, - color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.neutral120) : Colors.grey, + color: selectedTaskType != null ? AppColor.icon2Color(context) : Colors.grey, ), 8.width, //we can change the implementation when merge with uiux branch _scannedLocationData == null ? "Scan QR Code".bodyText(context).custom( - color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.black20) : Colors.grey, + color: selectedTaskType != null ? AppColor.icon2Color(context) : Colors.grey, ) : 'Re Scan QR Code'.bodyText(context).custom( - color: selectedTaskType != null ? (context.isDark ? AppColor.neutral20 : AppColor.black20) : Colors.grey, + color: selectedTaskType != null ? AppColor.icon2Color(context) : Colors.grey, ), ], ), From 03ea176ca1ed44954a3e5f9705eb4d4d22936fd6 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Jul 2026 10:05:03 +0300 Subject: [PATCH 69/73] ui ux improvement --- .../pages/asset_delivery_line_card_view.dart | 59 ++--- .../pages/asset_delivery_page.dart | 8 +- .../pages/asset_delivery_table_card_view.dart | 22 +- .../cost_center_form_view.dart | 229 +++++++++--------- .../delivery_inspection_form_view.dart | 126 ++-------- .../inpection_bottomsheet.dart | 11 +- .../oracle_user_autocomplete_field.dart | 16 +- .../pages/history_log_view.dart | 4 +- .../asset_auto_complete_field.dart | 16 +- .../widgets/date_and_time/date_picker.dart | 2 +- .../widgets/e_signature/e_signature.dart | 11 +- 11 files changed, 186 insertions(+), 318 deletions(-) diff --git a/lib/modules/asset_delivery_module/pages/asset_delivery_line_card_view.dart b/lib/modules/asset_delivery_module/pages/asset_delivery_line_card_view.dart index 5337f457..e953eb3d 100644 --- a/lib/modules/asset_delivery_module/pages/asset_delivery_line_card_view.dart +++ b/lib/modules/asset_delivery_module/pages/asset_delivery_line_card_view.dart @@ -4,6 +4,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/asset_delivery_module/models/delivery_line_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; class AssetDeliveryLineCard extends StatelessWidget { DeliveryLineModel deliveryLineModel; @@ -28,59 +29,27 @@ class AssetDeliveryLineCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Item Category: ${deliveryLineModel.itemCategory ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), + InfoTextWidget(label: "Item Category", value: deliveryLineModel.itemCategory ?? '-'), if (!isViewOnly) - 'edit_icon'.toSvgAsset().onPress(() { + 'edit_icon'.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)).onPress(() { if (editPress != null) { editPress!(); } }), ], ), - Text( - 'Item No: ${deliveryLineModel.itemNumber ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Item Description: ${deliveryLineModel.itemDescription ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Quantity: ${deliveryLineModel.qty ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - - if(!isTechnical)...[ - Text( - 'Remaining Quantity: ${deliveryLineModel.remainingQty ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Quantity of this Delivery: ${deliveryLineModel.usingQtyInThisDelivery ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), + InfoTextWidget(label: "Item No", value: deliveryLineModel.itemNumber ?? '-'), + InfoTextWidget(label: "Item Description", value: deliveryLineModel.itemDescription ?? '-'), + InfoTextWidget(label: "Quantity", value: deliveryLineModel.qty?.toString() ?? '-'), + if (!isTechnical) ...[ + InfoTextWidget(label: "Remaining Quantity", value: deliveryLineModel.remainingQty?.toString() ?? '-'), + InfoTextWidget(label: "Quantity of this Delivery", value: deliveryLineModel.usingQtyInThisDelivery?.toString() ?? '-'), ], - - Text( - 'Measure Unit: ${deliveryLineModel.unitOfMeasure ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Unit price: ${deliveryLineModel.unitPrice ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Line Type: ${deliveryLineModel.lineType ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Total: ${deliveryLineModel.total ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), + InfoTextWidget(label: "Measure Unit", value: deliveryLineModel.unitOfMeasure?.toString() ?? '-'), + InfoTextWidget(label: "Unit price", value: deliveryLineModel.unitPrice?.toString() ?? '-'), + InfoTextWidget(label: "Line Type", value: deliveryLineModel.lineType?.toString() ?? '-'), + InfoTextWidget(label: "Total", value: deliveryLineModel.total?.toString() ?? '-'), ], - ).toShadowContainer(context, borderRadius: 20, padding: 12); + ).toShadowContainer(context); } } diff --git a/lib/modules/asset_delivery_module/pages/asset_delivery_page.dart b/lib/modules/asset_delivery_module/pages/asset_delivery_page.dart index 52626bd9..2e6bd66f 100644 --- a/lib/modules/asset_delivery_module/pages/asset_delivery_page.dart +++ b/lib/modules/asset_delivery_module/pages/asset_delivery_page.dart @@ -101,7 +101,7 @@ class _AssetDeliveryPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - requestDetailCard(context).toShadowContainer(context, borderRadius: 20, padding: 0), + requestDetailCard(context).toShadowContainer(context), 16.height, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -160,7 +160,7 @@ class _AssetDeliveryPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoHeader16Widget('PO Details'.addTranslation), - InfoTextWidget(label: 'PO No. Internal'.addTranslation, value: widget.poInternal ?? '-'), + InfoTextWidget(label: 'PO No. Internal'.addTranslation, value: dataModel?.poNumber?.toString() ?? '-'), InfoTextWidget(label: 'Client Name'.addTranslation, value: dataModel?.clientName ?? '-'), // Row( // mainAxisSize: MainAxisSize.min, @@ -193,7 +193,7 @@ class _AssetDeliveryPageState extends State { InfoTextWidget(label: 'Total'.addTranslation, value: dataModel?.total != null ? dataModel!.total.toString() : '-'), InfoTextWidget(label: 'Total Invoice'.addTranslation, value: dataModel?.totalInvoice != null ? dataModel!.totalInvoice.toString() : '-'), ], - ).paddingAll(16); + ); } String getPOExternal() { @@ -218,7 +218,7 @@ class _AssetDeliveryPageState extends State { ? const NoDataFound().center : ListView.separated( itemCount: assetDeliveryTableList.length, - separatorBuilder: (_, __) => 8.height, + separatorBuilder: (_, __) => 12.height, padding: EdgeInsets.zero, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), diff --git a/lib/modules/asset_delivery_module/pages/asset_delivery_table_card_view.dart b/lib/modules/asset_delivery_module/pages/asset_delivery_table_card_view.dart index e7a41d96..59ed682a 100644 --- a/lib/modules/asset_delivery_module/pages/asset_delivery_table_card_view.dart +++ b/lib/modules/asset_delivery_module/pages/asset_delivery_table_card_view.dart @@ -7,6 +7,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/asset_delivery_module/models/asset_delivery_data_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; class AssetDeliveryTableCard extends StatelessWidget { @@ -54,24 +55,11 @@ class AssetDeliveryTableCard extends StatelessWidget { // ], // ), 8.height, - Text( - 'Delivery Number: ${deliveryTableModel.number ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Delivery Amount: ${deliveryTableModel.deliveryAmount ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Invoice Amount: ${deliveryTableModel.invoiceAmount ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Align( - alignment: Alignment.centerRight, - child: Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14), - ), + InfoTextWidget(label: "Delivery Number", value: deliveryTableModel.number ?? '-'), + InfoTextWidget(label: "Delivery Amount", value: deliveryTableModel.deliveryAmount?.toString() ?? '-'), + InfoTextWidget(label: "Invoice Amount", value: deliveryTableModel.invoiceAmount?.toString() ?? '-', showArrow: true), ], - ).toShadowContainer(context, borderRadius: 20).onPress(() { + ).toShadowContainer(context).onPress(() { onCardTap(); }); } diff --git a/lib/modules/asset_delivery_module/pages/delivery_inspection/cost_center_form_view.dart b/lib/modules/asset_delivery_module/pages/delivery_inspection/cost_center_form_view.dart index b272c886..1b3a4337 100644 --- a/lib/modules/asset_delivery_module/pages/delivery_inspection/cost_center_form_view.dart +++ b/lib/modules/asset_delivery_module/pages/delivery_inspection/cost_center_form_view.dart @@ -18,6 +18,7 @@ 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/single_item_drop_down_menu.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; class CostCenterFormView extends StatefulWidget { final List? costCenterList; @@ -113,44 +114,53 @@ class _CostCenterFormViewState extends State { ), body: Consumer(builder: (context, provider, child) { return provider.isLoading - ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center + ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center : Column( - children: [ - itemDetails(), - selectionButton(), - _selectedType == CostCenterType.costCenter ? _buildCostCenterList() : _buildSerialNumberList(), - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - buttonColor: AppColor.primary10, - label: 'Save'.addTranslation, - maxWidth: true, - onPressed: () async { - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - await assetDeliveryProvider - .saveCostCenterToLineDeliveryInspection(tableItemId: widget.tableItemId, lineId: widget.lineId, list: formModel.costCenters, serialNoList: formModel.serialNos) - .then((status) async { - Navigator.pop(context); - if (status) { - Navigator.pop(context); - assetDeliveryProvider.getDeliveryLinesListById( - itemId: widget.tableItemId, - ); - } - }); - }), - ), - ], - ); + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + itemDetails(), + 12.height, + selectionButton(), + 12.height, + _selectedType == CostCenterType.costCenter ? _buildCostCenterList() : _buildSerialNumberList(), + ], + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: 'Save'.addTranslation, + maxWidth: true, + onPressed: () async { + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + await assetDeliveryProvider + .saveCostCenterToLineDeliveryInspection(tableItemId: widget.tableItemId, lineId: widget.lineId, list: formModel.costCenters, serialNoList: formModel.serialNos) + .then((status) async { + Navigator.pop(context); + if (status) { + Navigator.pop(context); + assetDeliveryProvider.getDeliveryLinesListById( + itemId: widget.tableItemId, + ); + } + }); + }), + ), + ], + ); }), ); } Widget _buildCostCenterList() { - return ListView.builder( + return ListView.separated( itemCount: formModel.costCenters!.length + 1, shrinkWrap: true, - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16, top: 8), + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) => 12.height, itemBuilder: (context, index) { if (index == formModel.costCenters!.length) { return AppFilledButton( @@ -184,10 +194,12 @@ class _CostCenterFormViewState extends State { } Widget _buildSerialNumberList() { - return ListView.builder( + return ListView.separated( itemCount: formModel.serialNos!.length + 1, shrinkWrap: true, - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16, top: 8), + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) => 12.height, itemBuilder: (context, index) { if (index == formModel.serialNos!.length) { return AppFilledButton( @@ -217,36 +229,33 @@ class _CostCenterFormViewState extends State { } Widget selectionButton() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - Expanded( - child: _radioItem( - value: CostCenterType.costCenter, - groupValue: _selectedType, - title: 'Cost Center', - onTap: () { - setState(() { - _selectedType = CostCenterType.costCenter; - }); - }, - ), + return Row( + children: [ + Expanded( + child: _radioItem( + value: CostCenterType.costCenter, + groupValue: _selectedType, + title: 'Cost Center', + onTap: () { + setState(() { + _selectedType = CostCenterType.costCenter; + }); + }, ), - Expanded( - child: _radioItem( - value: CostCenterType.serialNumber, - groupValue: _selectedType, - title: 'Serial Number', - onTap: () { - setState(() { - _selectedType = CostCenterType.serialNumber; - }); - }, - ), + ), + Expanded( + child: _radioItem( + value: CostCenterType.serialNumber, + groupValue: _selectedType, + title: 'Serial Number', + onTap: () { + setState(() { + _selectedType = CostCenterType.serialNumber; + }); + }, ), - ], - ), + ), + ], ); } @@ -263,6 +272,13 @@ class _CostCenterFormViewState extends State { children: [ Radio( value: value, + activeColor: AppColor.primary10, + fillColor: WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColor.primary10; // Thumb color when selected + } + return AppColor.icon2Color(context); // Thumb color when unselected (grey) + }), groupValue: groupValue, onChanged: (_) => onTap(), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -279,34 +295,12 @@ class _CostCenterFormViewState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, + spacing: 4, children: [ - Text( - 'Item No'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - widget.itemNo ?? '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), - 8.height, - Text( - 'Item Description'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - widget.itemDescription ?? '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), + InfoTextWidget(label: "Item No", value: "\n${widget.itemNo ?? '-'}"), + InfoTextWidget(label: "Item Description", value: "\n${widget.itemDescription ?? '-'}"), ], - ).toShadowContainer( - context, - borderRadius: 20, - padding: 12, - showShadow: false, - margin: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), - ); + ).toShadowContainer(context); } } @@ -354,34 +348,34 @@ class CostCenterItemCard extends StatelessWidget { children: [ isHmg == true ? SingleItemDropDownMenu( - context: context, - height: 56.toScreenHeight, - title: 'Cost Center'.addTranslation, - showShadow: false, - backgroundColor: AppColor.fieldBgColor(context), - requestById: requestId, - showAsBottomSheet: true, - matchBy: DropDownMatchType.name, - initialValue: model.costCenter, - onSelect: (status) { - if (status != null) { - model.costCenter = status; - model.costCenterName = status.name; - } - }, - ) + context: context, + height: 56.toScreenHeight, + title: 'Cost Center'.addTranslation, + showShadow: false, + backgroundColor: AppColor.fieldBgColor(context), + requestById: requestId, + showAsBottomSheet: true, + matchBy: DropDownMatchType.name, + initialValue: model.costCenter, + onSelect: (status) { + if (status != null) { + model.costCenter = status; + model.costCenterName = status.name; + } + }, + ) : AppTextFormField( - labelText: 'Cost Center'.addTranslation, - backgroundColor: AppColor.fieldBgColor(context), - initialValue: model.costCenterName ?? '', - textAlign: TextAlign.center, - labelStyle: AppTextStyles.textFieldLabelStyle, - showShadow: false, - onChange: (value) { - model.costCenterName = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), + labelText: 'Cost Center'.addTranslation, + backgroundColor: AppColor.fieldBgColor(context), + initialValue: model.costCenterName ?? '', + textAlign: TextAlign.center, + labelStyle: AppTextStyles.textFieldLabelStyle, + showShadow: false, + onChange: (value) { + model.costCenterName = value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), 8.height, AppTextFormField( labelText: 'Quantity'.addTranslation, @@ -402,7 +396,7 @@ class CostCenterItemCard extends StatelessWidget { ], ) ], - ).toShadowContainer(context, paddingObject: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)).paddingOnly(bottom: 12); + ).toShadowContainer(context); } } @@ -447,11 +441,6 @@ class SerialNumberItemCard extends StatelessWidget { style: Theme.of(context).textTheme.titleMedium, ), ], - ) - .toShadowContainer( - context, - paddingObject: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - ) - .paddingOnly(bottom: 12); + ).toShadowContainer(context); } } diff --git a/lib/modules/asset_delivery_module/pages/delivery_inspection/delivery_inspection_form_view.dart b/lib/modules/asset_delivery_module/pages/delivery_inspection/delivery_inspection_form_view.dart index 39e097b4..f62cd62c 100644 --- a/lib/modules/asset_delivery_module/pages/delivery_inspection/delivery_inspection_form_view.dart +++ b/lib/modules/asset_delivery_module/pages/delivery_inspection/delivery_inspection_form_view.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:date_picker_timeline/extra/color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -32,6 +33,8 @@ import 'package:test_sa/views/widgets/date_and_time/time_picker.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/acknowledge_work_dialog.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import '../../../../new_views/common_widgets/app_filled_button.dart'; @@ -97,23 +100,19 @@ class _DeliveryInspectionFormViewState extends State return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: isLoading - ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center + ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center : Form( key: _formKey, child: Column( children: [ SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ if (!viewOnly) ...[inspectionDetailsForm()] else ...[inspectionDetails()], - 20.height, - Text( - "Lines", - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, + InfoHeader16Widget("Lines"), linesList(context), ], )).expanded, @@ -178,12 +177,12 @@ class _DeliveryInspectionFormViewState extends State return Consumer(builder: (context, provider, child) { List linesList = provider.deliveryLinesList; return provider.isDeliveryLineListLoading - ? SizedBox.expand(child: CircularProgressIndicator(color: AppColor.loadingColor(context)).center) + ? SizedBox.expand(child: CircularProgressIndicator(color: AppColor.loadingColor(context)).center) : linesList.isEmpty ? const NoDataFound().center : ListView.separated( itemCount: linesList.length, - separatorBuilder: (_, __) => 8.height, + separatorBuilder: (_, __) => 12.height, padding: EdgeInsets.zero, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -234,6 +233,7 @@ class _DeliveryInspectionFormViewState extends State Widget inspectionDetailsForm() { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ _inspectionTile( title: 'Inspected by'.addTranslation, @@ -251,7 +251,6 @@ class _DeliveryInspectionFormViewState extends State } }, ), - 8.height, _inspectionTile( title: 'Approved by'.addTranslation, model: model.approvedByModel, @@ -269,7 +268,6 @@ class _DeliveryInspectionFormViewState extends State }, ), if (widget.requestModel?.paymentTerm?.value == 2) ...[ - 8.height, _inspectionTile( title: 'ThirdApprover'.addTranslation, model: model.thirdApprovalModel, @@ -287,7 +285,6 @@ class _DeliveryInspectionFormViewState extends State }, ), ], - 8.height, ADatePicker( label: 'Inspected Date/Time From'.addTranslation, hideShadow: true, @@ -314,12 +311,10 @@ class _DeliveryInspectionFormViewState extends State if (!_validateInspectionDates(from: fromDateTime, to: model.deliveryInspectionInspectedDateTo)) { return; // invalid, do not assign } - model.deliveryInspectionInspectedDateFrom = fromDateTime; setState(() {}); }, ), - 8.height, ADatePicker( label: 'Inspected Date/Time To'.addTranslation, hideShadow: true, @@ -356,18 +351,7 @@ class _DeliveryInspectionFormViewState extends State setState(() {}); }, ), - 8.height, - Text( - 'Total Time'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - formatInspectionDuration(model.totalTime), - style: AppTextStyles.bodyText2.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, - ), - ), + InfoTextWidget(label: 'Total Time'.addTranslation, value: formatInspectionDuration(model.totalTime)), ], ).toShadowContainer(context, borderRadius: 20, padding: 12); } @@ -421,64 +405,18 @@ class _DeliveryInspectionFormViewState extends State return '${hours.toStringAsFixed(2)} hours'; } - Widget inspectionDetails() { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ - Text( - 'Inspected by'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - model.deliveryInspectionInspectedBy ?? '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), - 8.height, - Text( - 'Approved by'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - model.deliveryInspectionApprovedBy ?? '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), - 8.height, - Text( - 'Inspected Date/Time From'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - model.deliveryInspectionInspectedDateFrom != null ? model.deliveryInspectionInspectedDateFrom!.toString().toServiceRequestDetailsFormat : '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), - 8.height, - Text( - 'Inspected Date/Time To'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - model.deliveryInspectionInspectedDateTo != null ? model.deliveryInspectionInspectedDateTo!.toString().toServiceRequestDetailsFormat : '-', - style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral120), - ), - 8.height, - Text( - 'Total Time'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), - 8.height, - Text( - formatInspectionDuration(model.totalTime), - style: AppTextStyles.bodyText2.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, - ), - ), + InfoTextWidget(label: "Inspected by", value: "\n${model.deliveryInspectionInspectedBy ?? '-'}"), + InfoTextWidget(label: "Approved by", value: "\n${model.deliveryInspectionApprovedBy ?? '-'}"), + InfoTextWidget(label: 'Inspected Date/Time From'.addTranslation, value: "\n${model.deliveryInspectionInspectedDateFrom?.toString().toServiceRequestDetailsFormat ?? '-'}"), + InfoTextWidget(label: 'Inspected Date/Time To'.addTranslation, value: "\n${model.deliveryInspectionInspectedDateTo?.toString().toServiceRequestDetailsFormat ?? '-'}"), + InfoTextWidget(label: 'Total Time'.addTranslation, value: "\n${formatInspectionDuration(model.totalTime) ?? '-'}"), ], - ).toShadowContainer(context, borderRadius: 20, padding: 12); + ).toShadowContainer(context); } Widget _inspectionTile({ @@ -493,8 +431,9 @@ class _DeliveryInspectionFormViewState extends State child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(12), + color: AppColor.background(context), + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), ), child: Row( children: [ @@ -502,25 +441,9 @@ class _DeliveryInspectionFormViewState extends State crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(title, style: AppTextStyles.textFieldLabelStyle), - 6.height, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - model?.name?.isNotEmpty == true ? model!.name! : 'Tap to add'.addTranslation, - style: AppTextStyles.bodyText, - ), - - ///Need to display other info as well - // Text( - // model?.email?.isNotEmpty == true - // ? model!.email! - // : '', - // style: AppTextStyles.bodyText, - // ), - ], - ), + InfoTextLabelWidget(label: title, textStyle: AppTextStyles.textFieldLabelStyle), + 4.height, + InfoTextLabelWidget(label: model?.name?.isNotEmpty == true ? model!.name! : 'Tap to add'.addTranslation, textStyle: AppTextStyles.bodyText), ], ), ], @@ -630,6 +553,7 @@ class _DeliveryInspectionFormViewState extends State return showModalBottomSheet( context: context, isScrollControlled: true, + backgroundColor: AppColor.bottomSheetColor(context), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), diff --git a/lib/modules/asset_delivery_module/pages/delivery_inspection/inpection_bottomsheet.dart b/lib/modules/asset_delivery_module/pages/delivery_inspection/inpection_bottomsheet.dart index 34aad6d3..130a2d6b 100644 --- a/lib/modules/asset_delivery_module/pages/delivery_inspection/inpection_bottomsheet.dart +++ b/lib/modules/asset_delivery_module/pages/delivery_inspection/inpection_bottomsheet.dart @@ -67,14 +67,14 @@ class _InspectionPersonBottomSheetState extends State { @override Widget build(BuildContext context) { assetDeliveryProvider ??= Provider.of(context); - final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(16)); return Container( decoration: BoxDecoration( color: AppColor.background(context), - borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], ), child: Autocomplete( @@ -73,7 +74,7 @@ class _AutoCompletePartsFieldState extends State { return TextField( controller: fieldTextEditingController, focusNode: fieldFocusNode, - style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 14, color: AppColor.textStyleColor(context)), textAlign: TextAlign.start, decoration: InputDecoration( border: border, @@ -81,15 +82,16 @@ class _AutoCompletePartsFieldState extends State { focusedBorder: border, enabledBorder: border, errorBorder: border, - contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + contentPadding: EdgeInsets.symmetric(vertical: 12.toScreenHeight, horizontal: 16.toScreenWidth), constraints: const BoxConstraints(), suffixIconConstraints: const BoxConstraints(minWidth: 0), filled: true, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(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: widget.label, - labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), + floatingLabelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.floatingTextStyleColor(context)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.labelTextStyleColor(context)), + ), textInputAction: TextInputAction.search, onChanged: (text) { diff --git a/lib/modules/asset_delivery_module/pages/history_log_view.dart b/lib/modules/asset_delivery_module/pages/history_log_view.dart index 9ad8c1fc..01cc5f37 100644 --- a/lib/modules/asset_delivery_module/pages/history_log_view.dart +++ b/lib/modules/asset_delivery_module/pages/history_log_view.dart @@ -61,8 +61,8 @@ class _AssetDeliveryHistoryLogViewState extends State { @override Widget build(BuildContext context) { - final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(16)); 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)], + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), ), child: Autocomplete( optionsBuilder: (TextEditingValue textEditingValue) async { @@ -90,7 +90,7 @@ class _AssetAutoCompleteFieldState extends State { return TextField( controller: _controller, focusNode: fieldFocusNode, - style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 14, color: AppColor.textStyleColor(context)), textAlign: TextAlign.start, decoration: InputDecoration( border: border, @@ -98,15 +98,15 @@ class _AssetAutoCompleteFieldState extends State { focusedBorder: border, enabledBorder: border, errorBorder: border, - contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + contentPadding: EdgeInsets.symmetric(vertical: 12.toScreenHeight, horizontal: 16.toScreenWidth), constraints: const BoxConstraints(), suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8), filled: true, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(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), + floatingLabelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.floatingTextStyleColor(context)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.labelTextStyleColor(context)), labelText: "Name/Oracle Code", - labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), suffixIcon: loading ? CircularProgressIndicator(color: AppColor.loadingColor(context), strokeWidth: 3.0).paddingOnly(end: 8) : null, ), textInputAction: TextInputAction.search, diff --git a/lib/views/widgets/date_and_time/date_picker.dart b/lib/views/widgets/date_and_time/date_picker.dart index 8fbb1264..f2439844 100644 --- a/lib/views/widgets/date_and_time/date_picker.dart +++ b/lib/views/widgets/date_and_time/date_picker.dart @@ -57,7 +57,7 @@ class ADatePicker extends StatelessWidget { children: [ Text( label, - style: AppTextStyles.tinyFont.copyWith(color: AppColor.floatingTextStyleColor(context)), + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.floatingTextStyleColor(context)), ), 4.height, (date == null diff --git a/lib/views/widgets/e_signature/e_signature.dart b/lib/views/widgets/e_signature/e_signature.dart index 64d297e3..63615089 100644 --- a/lib/views/widgets/e_signature/e_signature.dart +++ b/lib/views/widgets/e_signature/e_signature.dart @@ -8,6 +8,7 @@ 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/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import '../loaders/image_loader.dart'; @@ -67,18 +68,16 @@ class _ESignatureState extends State { width: MediaQuery.of(context).size.width, padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth, vertical: 8.toScreenHeight), decoration: BoxDecoration( - color: widget.backgroundColor ?? (context.isDark ? AppColor.background(context) : Colors.white), - borderRadius: _editable ? const BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)) : BorderRadius.circular(10), + color: AppColor.dropDownBackground(context), + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), boxShadow: widget.showShadow ? [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)] : null, ), child: widget.editable == false ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Signature'.addTranslation, - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), - ), + InfoTextLabelWidget(label: "Signature", textStyle: AppTextStyles.bodyText), Container( height: 135.toScreenHeight, width: MediaQuery.of(context).size.width, From 3d8d343a23e925fc45ffdad4ec52982f6bf527ab Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Jul 2026 10:43:19 +0300 Subject: [PATCH 70/73] v1.8.2+48 uploaded. --- lib/controllers/api_routes/urls.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index d532385f..aee664aa 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,7 +1,7 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "47"; + static const String appReleaseBuildNumber = "48"; static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url diff --git a/pubspec.yaml b/pubspec.yaml index 3d3b693d..40585736 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.8.1+47 +version: 1.8.2+48 environment: sdk: ">=3.5.0 <4.0.0" From f4643920df73395a4c21bd78e07509023f695c2e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 29 Jul 2026 10:10:16 +0300 Subject: [PATCH 71/73] improvements --- lib/modules/tm_module/tasks/task_request_detail_view.dart | 2 +- lib/new_views/pages/land_page/my_request/my_requests_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/tm_module/tasks/task_request_detail_view.dart b/lib/modules/tm_module/tasks/task_request_detail_view.dart index 326c3989..a382bddb 100644 --- a/lib/modules/tm_module/tasks/task_request_detail_view.dart +++ b/lib/modules/tm_module/tasks/task_request_detail_view.dart @@ -163,7 +163,7 @@ class _TaskRequestDetailsViewState extends State { ], buildTechnicalComments(taskModel: taskModel), ], - ).toShadowContainer(context, padding: 16), + ).toShadowContainer(context), ).expanded, if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == false && context.settingProvider.isUserFlowMedical && 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 227d46f1..65e635ea 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 @@ -606,7 +606,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid case 8: return 'Asset Inventory'; case 9: - return 'TRAF'; + return 'Assessment of Need & Technology'; case 10: return 'Equipment Internal Audit'; case 11: From a08a578ef460269b07ac8fca61ef661e3564b07b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 29 Jul 2026 11:09:22 +0300 Subject: [PATCH 72/73] improvements --- lib/dashboard_latest/dashboard_provider.dart | 4 ++-- .../asset_delivery_module/pages/history_log_view.dart | 5 +++-- .../cm_module/views/components/history_log_view.dart | 8 +++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/dashboard_latest/dashboard_provider.dart b/lib/dashboard_latest/dashboard_provider.dart index c50e9039..a25a10fe 100644 --- a/lib/dashboard_latest/dashboard_provider.dart +++ b/lib/dashboard_latest/dashboard_provider.dart @@ -329,7 +329,7 @@ class DashBoardProvider extends ChangeNotifier { if (!statusTransaction.contains(5)) statusTransaction.add(5); break; case RequestTypeEnum.pendingAcknowledgment: - if (!statusTransaction.contains(3)) statusTransaction.add(3); + if (!statusTransaction.contains(4)) statusTransaction.add(4); break; case RequestTypeEnum.unassigned: case RequestTypeEnum.highPriority: @@ -605,7 +605,7 @@ class CategoryTabs { tabs.add(CategoryTabs(label: 'Open', tag: 1, id: 2, requestType: RequestTypeEnum.open, icon: "dashboard/open_icon", iconColor: AppColor.openColor)); tabs.add(CategoryTabs(label: 'In Progress', tag: 2, id: 3, requestType: RequestTypeEnum.inProgress, icon: "dashboard/inprogress_icon", iconColor: AppColor.primary10)); // tabs.add(CategoryTabs(label: 'Pending Acknowledgement', tag: 3, id: 1, requestType: RequestTypeEnum.pendingAcknowledgment, icon: "complete_request", iconColor: AppColor.overdueColor)); - tabs.add(CategoryTabs(label: 'Acknowledgement', tag: 3, id: 1, requestType: RequestTypeEnum.pendingAcknowledgment, icon: "complete_request", iconColor: AppColor.overdueColor)); + tabs.add(CategoryTabs(label: 'Acknowledgement', tag: 4, id: 1, requestType: RequestTypeEnum.pendingAcknowledgment, icon: "complete_request", iconColor: AppColor.overdueColor)); tabs.add(CategoryTabs(label: 'Canceled', tag: 6, id: 4, requestType: RequestTypeEnum.canceled, icon: "dashboard/unassigned", iconColor: AppColor.red30)); return tabs; } diff --git a/lib/modules/asset_delivery_module/pages/history_log_view.dart b/lib/modules/asset_delivery_module/pages/history_log_view.dart index 01cc5f37..db0e4757 100644 --- a/lib/modules/asset_delivery_module/pages/history_log_view.dart +++ b/lib/modules/asset_delivery_module/pages/history_log_view.dart @@ -10,6 +10,7 @@ import 'package:test_sa/modules/asset_delivery_module/provider/asset_delivery_pr import 'package:test_sa/modules/cm_module/cm_request_utils.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/item_views/info_header_widget.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; class AssetDeliveryHistoryLogView extends StatefulWidget { @@ -126,7 +127,7 @@ class _AssetDeliveryHistoryLogViewState extends State Date: Wed, 29 Jul 2026 12:30:22 +0300 Subject: [PATCH 73/73] v1.8.3+49 uploaded --- lib/controllers/api_routes/urls.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index aee664aa..07f576bd 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,7 +1,7 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "48"; + static const String appReleaseBuildNumber = "49"; static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url diff --git a/pubspec.yaml b/pubspec.yaml index 40585736..5a11ebf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.8.2+48 +version: 1.8.3+49 environment: sdk: ">=3.5.0 <4.0.0"