diff --git a/assets/images/svg/critical_low_result.svg b/assets/images/svg/critical_low_result.svg
new file mode 100644
index 0000000..2706852
--- /dev/null
+++ b/assets/images/svg/critical_low_result.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/graph.svg b/assets/images/svg/graph.svg
new file mode 100644
index 0000000..8c79844
--- /dev/null
+++ b/assets/images/svg/graph.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/ic_date_filter.svg b/assets/images/svg/ic_date_filter.svg
new file mode 100644
index 0000000..43d28f2
--- /dev/null
+++ b/assets/images/svg/ic_date_filter.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/images/svg/ic_list.svg b/assets/images/svg/ic_list.svg
new file mode 100644
index 0000000..e68f20b
--- /dev/null
+++ b/assets/images/svg/ic_list.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/low_result.svg b/assets/images/svg/low_result.svg
new file mode 100644
index 0000000..52a2ef1
--- /dev/null
+++ b/assets/images/svg/low_result.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/normal_result.svg b/assets/images/svg/normal_result.svg
new file mode 100644
index 0000000..abe036f
--- /dev/null
+++ b/assets/images/svg/normal_result.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/range_calender.svg b/assets/images/svg/range_calender.svg
new file mode 100644
index 0000000..940c002
--- /dev/null
+++ b/assets/images/svg/range_calender.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/refernce_range_indicator.svg b/assets/images/svg/refernce_range_indicator.svg
new file mode 100644
index 0000000..a3508b9
--- /dev/null
+++ b/assets/images/svg/refernce_range_indicator.svg
@@ -0,0 +1,7 @@
+
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index be651b7..dd4bce4 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -7,6 +7,7 @@ class AppAssets {
static const String arrow_forward = '$svgBasePath/arrow_forward.svg';
static const String externalLink = '$svgBasePath/external_link.svg';
static const String calendar = '$svgBasePath/calendar.svg';
+ static const String rangeCalendar = '$svgBasePath/range_calender.svg';
static const String hmc = '$svgBasePath/hmc.svg';
static const String ksa = '$svgBasePath/ksa.svg';
static const String sms = '$svgBasePath/sms.svg';
@@ -133,6 +134,13 @@ class AppAssets {
static const String minus = '$svgBasePath/minus.svg';
static const String home_lab_result_icon = '$svgBasePath/home_lab_result_icon.svg';
static const String visa_mastercard_icon = '$svgBasePath/visa_mastercard.svg';
+ static const String lab_result_indicator = '$svgBasePath/refernce_range_indicator.svg';
+ static const String ic_date_filter = '$svgBasePath/ic_date_filter.svg';
+ static const String ic_list = '$svgBasePath/ic_list.svg';
+ static const String ic_graph = '$svgBasePath/graph.svg';
+ static const String ic_normal_result = '$svgBasePath/normal_result.svg';
+ static const String ic_low_result = '$svgBasePath/low_result.svg';
+ static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg';
//bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg';
diff --git a/lib/core/common_models/data_points.dart b/lib/core/common_models/data_points.dart
index af7c473..89fa6e1 100644
--- a/lib/core/common_models/data_points.dart
+++ b/lib/core/common_models/data_points.dart
@@ -6,9 +6,22 @@ class DataPoint {
final double value;
///label shown on the bottom of the graph
String label;
+ String refernceValue;
+ String actualValue;
+ DateTime time;
+ String displayTime;
DataPoint(
{required this.value,
required this.label,
+ required this.refernceValue,
+ required this.actualValue,
+ required this.time,
+ required this.displayTime,
});
+
+ @override
+ String toString() {
+ return "the time is $time";
+ }
}
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 4de7c5f..b42706f 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -100,6 +100,7 @@ class AppDependencies {
() => LabViewModel(
labRepo: getIt(),
errorHandlerService: getIt(),
+ navigationService: getIt()
),
);
diff --git a/lib/features/lab/history/lab_history_viewmodel.dart b/lib/features/lab/history/lab_history_viewmodel.dart
new file mode 100644
index 0000000..a377b52
--- /dev/null
+++ b/lib/features/lab/history/lab_history_viewmodel.dart
@@ -0,0 +1,7 @@
+
+
+import 'package:flutter/material.dart';
+
+class LabHistoryViewModel extends ChangeNotifier{
+ bool isGraphShowing = false;
+}
\ No newline at end of file
diff --git a/lib/features/lab/lab_range_view_model.dart b/lib/features/lab/lab_range_view_model.dart
new file mode 100644
index 0000000..aa91964
--- /dev/null
+++ b/lib/features/lab/lab_range_view_model.dart
@@ -0,0 +1,93 @@
+import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/features/lab/models/Range.dart';
+
+class LabRangeViewModel extends ChangeNotifier {
+ List months = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'April',
+ 'May',
+ 'Jun',
+ 'July',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+ ];
+ bool isGraphVisible = true;
+ Range? _currentlySelectedRange;
+
+ Range? get currentlySelectedRange => _currentlySelectedRange;
+
+ set currentlySelectedRange(Range? value) {
+ _currentlySelectedRange = value;
+ notifyListeners();
+
+ }
+
+ DateTime? _toDate;
+
+ DateTime? get toDate => _toDate;
+
+ set toDate(DateTime? value) {
+ _toDate = value;
+ notifyListeners();
+ }
+
+ DateTime? _fromDate;
+
+ DateTime? get fromDate => _fromDate;
+
+ set fromDate(DateTime? value) {
+ _fromDate = value;
+ notifyListeners();
+
+ }
+
+ LabRangeViewModel();
+
+ get getCurrentYear => DateTime.now().year;
+
+ calculateDatesFromRange() {
+ _toDate = DateTime.now();
+ switch (_currentlySelectedRange) {
+ case Range.WEEKLY:
+ _fromDate = _toDate!.subtract(Duration(days: 7));
+ case Range.LAST_MONTH:
+ _fromDate = _toDate!.subtract(Duration(days: 30));
+ case Range.LAST_6MONTH:
+ _fromDate = _toDate!.subtract(Duration(days: (30 * 6)));
+ case Range.THIS_YEAR:
+ _toDate = DateTime(_toDate!.year, DateTime.december, 31);
+ _fromDate = DateTime(_toDate!.year, DateTime.january, 01);
+ default:
+ }
+ }
+
+ getDateString(DateTime? date){
+ if(date == null) return "-";
+
+ String year = date.year.toString().substring(2);
+ return '${date.day} ${months[date.month-1]},$year';
+ }
+
+ flush(){
+ toDate = null;
+ fromDate = null;
+ currentlySelectedRange = null;
+ isGraphVisible = true;
+ notifyListeners();
+ }
+
+ resetCurrentlySelectedRange(){
+ currentlySelectedRange = null;
+ }
+
+ alterGraphVisibility(){
+ isGraphVisible = !isGraphVisible;
+ notifyListeners();
+ }
+}
diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart
index f205494..36f9767 100644
--- a/lib/features/lab/lab_repo.dart
+++ b/lib/features/lab/lab_repo.dart
@@ -6,8 +6,11 @@ import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
+import 'models/resp_models/lab_result.dart' show LabResult;
+
abstract class LabRepo {
Future>>> getPatientLabOrders();
+ Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName);
}
class LabRepoImp implements LabRepo {
@@ -19,7 +22,6 @@ class LabRepoImp implements LabRepo {
@override
Future>>> getPatientLabOrders() async {
Map mapDevice = {};
-
try {
GenericApiModel>? apiResponse;
Failure? failure;
@@ -56,4 +58,56 @@ class LabRepoImp implements LabRepo {
return Left(UnknownFailure(e.toString()));
}
}
+
+ @override
+ Future>>> getPatientLabResults(
+ PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName
+ ) async {
+
+ Map request = Map();
+ request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
+ request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
+ request['OrderNo'] = laborder!.orderNo;
+ request['isDentalAllowedBackend'] = false;
+ request['SetupID'] = laborder!.setupID;
+ request['ProjectID'] = laborder.projectID;
+ request['ClinicID'] = laborder.clinicID;
+ request['Procedure'] = procedureName;
+ request['LanguageID'] = 1;
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_Patient_LAB_ORDERS_RESULT,
+ body: request,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['ListPLR'];
+ if (list == null || list.isEmpty) {
+ throw Exception("lab list is empty");
+ }
+
+ final labOrders = list.map((item) => LabResult.fromJson(item as Map)).toList().cast();
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: labOrders,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
}
diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart
index 29b90f8..811e5f8 100644
--- a/lib/features/lab/lab_view_model.dart
+++ b/lib/features/lab/lab_view_model.dart
@@ -1,7 +1,20 @@
+import 'dart:core';
+
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
+import 'package:hmg_patient_app_new/core/utils/date_util.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
+import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
+import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:intl/intl.dart' show DateFormat;
+import 'package:logger/logger.dart';
class LabViewModel extends ChangeNotifier {
bool isLabOrdersLoading = false;
@@ -9,17 +22,42 @@ class LabViewModel extends ChangeNotifier {
LabRepo labRepo;
ErrorHandlerService errorHandlerService;
+ NavigationService navigationService;
List patientLabOrders = [];
List filteredLabOrders = [];
List tempLabOrdersList = [];
+
+ List mainLabResults = [];
+ List mainGraphPoints = [];
+ List filteredGraphValues = [];
+ List months = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'April',
+ 'May',
+ 'Jun',
+ 'July',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+ ];
+
late List _labSuggestionsList = [];
List get labSuggestions => _labSuggestionsList;
Set uniqueTests = {};
- LabViewModel({required this.labRepo, required this.errorHandlerService});
+ double maxYForThreeDots = 0.0;
+
+ LabViewModel(
+ {required this.labRepo,
+ required this.errorHandlerService,
+ required this.navigationService});
initLabProvider() {
patientLabOrders.clear();
@@ -84,9 +122,321 @@ class LabViewModel extends ChangeNotifier {
uniqueTests = {
for (var item in patientLabOrders)
if (item.testDetails != null)
- ...?item.testDetails?.map((test) =>
- TestDetails(description: test.description.toString(), testCode: test.testCode.toString(), testID: test.testID, createdOn: item.createdOn))
+ ...?item.testDetails?.map((test) => TestDetails(
+ description: test.description.toString(),
+ testCode: test.testCode.toString(),
+ testID: test.testID,
+ createdOn: item.createdOn,
+ model: item))
};
- uniqueTests.forEach(print);
+ }
+
+ Future getPatientLabResult(
+ PatientLabOrdersResponseModel laborder, String procedureName) async {
+ LoaderBottomSheet.showLoader();
+ mainLabResults.clear();
+ filteredGraphValues.clear();
+
+ final result = await labRepo.getPatientLabResults(
+ laborder,
+ Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")),
+ procedureName);
+
+ result.fold(
+ (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await errorHandlerService.handleError(failure: failure);
+ },
+ (apiResponse) {
+ LoaderBottomSheet.hideLoader();
+ if (apiResponse.messageStatus == 2) {
+ } else if (apiResponse.messageStatus == 1) {
+ var sortedResponse = sortByFlagAndValue(apiResponse.data!);
+ var recentThree = sort(sortedResponse);
+ mainLabResults = recentThree;
+
+ double counter = 1;
+ recentThree.reversed.forEach((element) {
+ try {
+ var dateTime =
+ DateUtil.convertStringToDate(element.verifiedOnDateTime!);
+ if (double.parse(element.resultValue!) > maxYForThreeDots) {
+ maxYForThreeDots = double.parse(element.resultValue!);
+ }
+ filteredGraphValues.add(DataPoint(
+ value: transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""),
+ actualValue:element.resultValue!,
+ label: formatDateAsMMYY(dateTime),
+ displayTime: resultDate(dateTime),
+ time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
+ refernceValue: element.calculatedResultFlag ?? "",
+
+ ));
+ counter++;
+ } catch (e) {}
+ });
+ LabResult recentResult = recentThree.first;
+ recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!));
+ navigationService.push(MaterialPageRoute(
+ builder: (_) =>
+ LabResultDetails(recentLabResult: recentResult)));
+ notifyListeners();
+ }
+ },
+ );
+ }
+
+ String resultDate(DateTime date){
+
+
+ return '${date.day} ${months[date.month-1]},${date.year}';
+ }
+
+ double transformValueInRange(double inputValue, String flag) {
+ // Define range boundaries
+ double rangeStart, rangeEnd;
+
+ switch (flag) {
+ case'LCL':
+ case 'CL':
+ rangeStart = 0.0;
+ rangeEnd = 19.0;
+ break;
+ case 'L':
+ rangeStart = 20.0;
+ rangeEnd = 39.0;
+ break;
+ case 'N':
+ rangeStart = 40.0;
+ rangeEnd = 59.0;
+ break;
+ case 'H':
+ rangeStart = 60.0;
+ rangeEnd = 79.0;
+ break;
+ case 'HCH':
+ case 'CH':
+ rangeStart = 80.0;
+ rangeEnd = 100.0;
+ break;
+ default:
+ throw ArgumentError('Invalid flag: $flag');
+ }
+
+ // Clamp input value to 0-100 and map it to the range bounds
+ final clampedValue = inputValue.clamp(0.0, 100.0);
+ final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1
+
+ // Map the normalized value to the target range bounds
+ final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart));
+
+ return transformedValue;
+ }
+ void getSelectedDateRange(DateTime? start, DateTime? end) {
+ if(start == null && end == null) {
+ print("the dates are null");
+ mainLabResults.forEach((element) {
+ final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
+ try{
+ filteredGraphValues.add(DataPoint(
+ value: transformValueInRange(double.parse(element.resultValue!),
+ element.calculatedResultFlag ?? ""),
+ actualValue: element.resultValue!,
+ label: formatDateAsMMYY(time),
+ displayTime: resultDate(time),
+ time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
+ refernceValue: element.calculatedResultFlag ?? "",
+ ));
+ }catch(e){
+
+ }
+ });
+
+ }else {
+ filteredGraphValues.clear();
+
+ mainLabResults.forEach((element) {
+ try {
+ var dateTime =
+ DateUtil.convertStringToDate(element.verifiedOnDateTime!);
+ if (start != null && end == null) {
+ if (dateTime.isAtSameMomentAs(start)) {
+
+ filteredGraphValues.add(DataPoint(
+ value: transformValueInRange(
+ double.parse(element.resultValue!),
+ element.calculatedResultFlag ?? ""),
+ actualValue: element.resultValue!,
+ label: formatDateAsMMYY(dateTime),
+ displayTime: resultDate(dateTime),
+ time:
+ DateUtil.convertStringToDate(element.verifiedOnDateTime),
+ refernceValue: element.calculatedResultFlag ?? ""));
+ }
+ } else if (start != null && end != null) {
+ if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) {
+
+ filteredGraphValues.add(DataPoint(
+ value: transformValueInRange(
+ double.parse(element.resultValue!),
+ element.calculatedResultFlag ?? ""),
+ actualValue: element.resultValue!,
+ label: formatDateAsMMYY(dateTime),
+ displayTime: resultDate(dateTime),
+ time:
+ DateUtil.convertStringToDate(element.verifiedOnDateTime),
+ refernceValue: element.calculatedResultFlag ?? ""));
+ }
+ }
+ } catch (e) {}
+ });
+ }
+ filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList();
+
+
+ notifyListeners();
+ }
+
+ String formatDateAsMMYY(DateTime date) {
+
+ String year = date.year.toString().substring(2);
+ return '${months[date.month-1]},$year';
+ }
+
+
+ List sortByFlagAndValue(List original) {
+ const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
+
+ int getFlagPriority(String? flag) {
+ if (flag == null) return priorityOrder.length;
+ final index = priorityOrder.indexOf(flag);
+ return index == -1 ? priorityOrder.length : index;
+ }
+
+ double parseResultValue(String? value) {
+ if (value == null) return double.nan;
+ return double.tryParse(value) ?? double.nan;
+ }
+
+ final copy = List.from(original);
+ copy.sort((a, b) {
+ final aFlagPriority = getFlagPriority(a.calculatedResultFlag);
+ final bFlagPriority = getFlagPriority(b.calculatedResultFlag);
+
+ if (aFlagPriority != bFlagPriority) {
+ return aFlagPriority.compareTo(bFlagPriority);
+ }
+
+ final aValue = parseResultValue(a.resultValue);
+ final bValue = parseResultValue(b.resultValue);
+
+ return aValue.compareTo(bValue);
+ });
+
+ return copy;
+ }
+
+ List sort(List original) {
+ DateTime? parseVerifiedDate(String? raw) {
+ if (raw == null) return null;
+ final regex = RegExp(r'\/Date\((\d+)\)\/');
+ final match = regex.firstMatch(raw);
+ if (match != null) {
+ final millis = int.tryParse(match.group(1)!);
+ if (millis != null) {
+
+ return DateTime.fromMillisecondsSinceEpoch(millis);
+ }
+ }
+ return null;
+ }
+
+ final copy = List.from(original);
+ copy.sort((a, b) {
+ final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime);
+ final bDate = DateUtil.convertStringToDate(b.verifiedOnDateTime);
+ final now = DateTime.now();
+ if (aDate == now && bDate == now) return 0;
+ if (aDate == now) return 1;
+ if (bDate == now) return -1;
+ return bDate.compareTo(aDate); // descending
+ });
+ return copy.toList();
+ }
+
+ List sortFilteredList(List original) {
+
+
+ final copy = List.from(original);
+ copy.sort((a, b) {
+ final aDate =a.time;
+ final bDate = a.time;
+ final now = DateTime.now();
+ if (aDate == now && bDate == now) return 0;
+ if (aDate == now) return 1;
+ if (bDate == now) return -1;
+ return bDate.compareTo(aDate); // descending
+ });
+ return copy.toList();
+ }
+
+ Color getColor(String flag) {
+ switch (flag) {
+ case 'LCL':
+ return AppColors.criticalLowAndHigh;
+ case 'CL':
+ return AppColors.criticalLowAndHigh;
+ case 'L':
+ return AppColors.highAndLow;
+ case 'N':
+ return AppColors.bgGreenColor;
+ case 'H':
+ return AppColors.highAndLow;
+ case 'CH':
+ return AppColors.criticalLowAndHigh;
+ case 'HCH':
+ return AppColors.criticalLowAndHigh;
+ default:
+ return Colors.grey;
+ }
+ }
+
+ String getFormattedDate(DateTime date){
+ return DateFormat('EEEE, dd MMMM. yyyy').format(date);
+ }
+
+ String getAssetUrlWRTResult(String refernceValue) {
+ switch (refernceValue) {
+ case 'CL':
+ case 'LCL':
+ return AppAssets.ic_critical_low_result;
+ case 'L':
+ return AppAssets.ic_low_result;
+ case 'N':
+ return AppAssets.ic_normal_result;
+ case 'H':
+ return AppAssets.ic_low_result;
+ case 'CH':
+ case 'HCH':
+ return AppAssets.ic_critical_low_result;
+ default:
+ return AppAssets.ic_normal_result;
+ }
+ }
+
+ bool getRotationWRTResult(String refernceValue) {
+ switch (refernceValue) {
+ case 'CL':
+ case 'LCL':
+ case 'L':
+ case 'N':
+ return false;
+ case 'H':
+ case 'CH':
+ case 'HCH':
+ return true;
+ default:
+ return true;
+ }
}
}
diff --git a/lib/features/lab/models/Range.dart b/lib/features/lab/models/Range.dart
new file mode 100644
index 0000000..5dd39dc
--- /dev/null
+++ b/lib/features/lab/models/Range.dart
@@ -0,0 +1,6 @@
+enum Range{
+ WEEKLY,
+ LAST_MONTH,
+ LAST_6MONTH,
+ THIS_YEAR,
+}
\ No newline at end of file
diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart
new file mode 100644
index 0000000..d4e9223
--- /dev/null
+++ b/lib/features/lab/models/resp_models/lab_result.dart
@@ -0,0 +1,116 @@
+class LabResult {
+ String? description;
+ dynamic femaleInterpretativeData;
+ int? gender;
+ int? lineItemNo;
+ dynamic maleInterpretativeData;
+ dynamic notes;
+ String? packageID;
+ int? patientID;
+ String? projectID;
+ String? referanceRange;
+ String? resultValue;
+ String? sampleCollectedOn;
+ String? sampleReceivedOn;
+ String? setupID;
+ dynamic superVerifiedOn;
+ String? testCode;
+ String? uOM;
+ String? verifiedOn;
+ String? verifiedOnDateTime;
+ String? changeResult;
+ String? calculatedResultFlag;
+ String? criticalHigh;
+ String? referenceHigh;
+ String? criticalLow;
+ String? referenceLow;
+
+ LabResult(
+ {this.description,
+ this.femaleInterpretativeData,
+ this.gender,
+ this.lineItemNo,
+ this.maleInterpretativeData,
+ this.notes,
+ this.packageID,
+ this.patientID,
+ this.projectID,
+ this.referanceRange,
+ this.resultValue,
+ this.sampleCollectedOn,
+ this.sampleReceivedOn,
+ this.setupID,
+ this.superVerifiedOn,
+ this.testCode,
+ this.uOM,
+ this.verifiedOn,
+ this.calculatedResultFlag,
+ this.verifiedOnDateTime,
+ this.criticalHigh,
+ this.referenceHigh,
+ this.criticalLow,
+ this.referenceLow,
+ });
+
+ LabResult.fromJson(Map json) {
+ description = json['Description'];
+ femaleInterpretativeData = json['FemaleInterpretativeData'];
+ gender = json['Gender'];
+ lineItemNo = json['LineItemNo'];
+ maleInterpretativeData = json['MaleInterpretativeData'];
+ notes = json['Notes'];
+ packageID = json['PackageID'];
+ patientID = json['PatientID'];
+ projectID = json['ProjectID'];
+ referanceRange = json['ReferanceRange'];
+ resultValue = json['ResultValue'];
+ sampleCollectedOn = json['SampleCollectedOn'];
+ sampleReceivedOn = json['SampleReceivedOn'];
+ setupID = json['SetupID'];
+ superVerifiedOn = json['SuperVerifiedOn'];
+ testCode = json['TestCode'];
+ uOM = json['UOM'];
+ verifiedOn = json['VerifiedOn'];
+ verifiedOnDateTime = json['VerifiedOnDateTime'];
+ changeResult = json['ChangeResult'];
+ calculatedResultFlag = json['CalculatedResultFlag'];
+ criticalHigh = json['CriticalHigh'];
+ referenceHigh = json['ReferenceHigh'];
+ criticalLow = json['CriticalLow'];
+ referenceLow = json['ReferenceLow'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['Description'] = this.description;
+ data['FemaleInterpretativeData'] = this.femaleInterpretativeData;
+ data['Gender'] = this.gender;
+ data['LineItemNo'] = this.lineItemNo;
+ data['MaleInterpretativeData'] = this.maleInterpretativeData;
+ data['Notes'] = this.notes;
+ data['PackageID'] = this.packageID;
+ data['PatientID'] = this.patientID;
+ data['ProjectID'] = this.projectID;
+ data['ReferanceRange'] = this.referanceRange;
+ data['ResultValue'] = this.resultValue;
+ data['SampleCollectedOn'] = this.sampleCollectedOn;
+ data['SampleReceivedOn'] = this.sampleReceivedOn;
+ data['SetupID'] = this.setupID;
+ data['SuperVerifiedOn'] = this.superVerifiedOn;
+ data['TestCode'] = this.testCode;
+ data['UOM'] = this.uOM;
+ data['VerifiedOn'] = this.verifiedOn;
+ data['VerifiedOnDateTime'] = this.verifiedOnDateTime;
+ data['ChangeResult'] = this.changeResult;
+ data['CriticalHigh'] = this.criticalHigh;
+ data['ReferenceHigh'] = this.referenceHigh;
+ data['CriticalLow'] = this.criticalLow;
+ data['ReferenceLow'] = this.referenceLow;
+ return data;
+ }
+
+ @override
+ String toString() {
+ return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)';
+ }
+}
diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart
index 265a19e..aadfc76 100644
--- a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart
+++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart
@@ -227,7 +227,8 @@ class TestDetails {
String? testCode;
String? testID;
String? createdOn;
- TestDetails({this.description, this.testCode, this.testID, this.createdOn});
+ PatientLabOrdersResponseModel? model;
+ TestDetails({this.description, this.testCode, this.testID, this.createdOn, this.model});
TestDetails.fromJson(Map json) {
description = json['Description'];
diff --git a/lib/main.dart b/lib/main.dart
index bdd1f29..3690c72 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -12,6 +12,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
+import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart';
+import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
@@ -78,7 +80,7 @@ void main() async {
create: (_) => LabViewModel(
labRepo: getIt(),
errorHandlerService: getIt(),
- ),
+ navigationService: getIt()),
),
ChangeNotifierProvider(
create: (_) => RadiologyViewModel(
@@ -148,7 +150,11 @@ void main() async {
),
ChangeNotifierProvider(
create: (_) => AppointmentViaRegionViewmodel(
- navigationService: getIt(), appState: getIt()))
+ navigationService: getIt(), appState: getIt())),
+ ChangeNotifierProvider(
+ create: (_) => LabHistoryViewModel()),
+ ChangeNotifierProvider(
+ create: (_) => LabRangeViewModel())
], child: MyApp()),
),
);
diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart
index 12bc8f5..65f5c11 100644
--- a/lib/presentation/home/landing_page.dart
+++ b/lib/presentation/home/landing_page.dart
@@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
+import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart
index e5c6929..bb0f391 100644
--- a/lib/presentation/lab/lab_order_by_test.dart
+++ b/lib/presentation/lab/lab_order_by_test.dart
@@ -29,56 +29,52 @@ class LabOrderByTest extends StatelessWidget {
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
- child: InkWell(
- onTap: () {
- if (!isLoading) {
- onTap();
- }
- },
- child: Container(
- key: ValueKey(index),
- padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // ...labOrder!.testDetails!.map((detail) {
- Padding(
- padding: EdgeInsets.only(bottom: 8.h),
- child: '${tests!.description}'.toText14(weight: FontWeight.w500),
- ),
+ child: Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // ...labOrder!.testDetails!.map((detail) {
+ Padding(
+ padding: EdgeInsets.only(bottom: 8.h),
+ child: '${tests!.description}'.toText14(weight: FontWeight.w500),
+ ),
- SizedBox(height: 12.h),
+ SizedBox(height: 12.h),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- AppCustomChipWidget(
- richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true),
- // chipType: ChipTypeEnum.lightBg,
- backgroundColor: AppColors.greyLightColor,
- textColor: AppColors.textColor,
- // borderRadius: 5,
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AppCustomChipWidget(
+ richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true),
+ // chipType: ChipTypeEnum.lightBg,
+ backgroundColor: AppColors.greyLightColor,
+ textColor: AppColors.textColor,
+ // borderRadius: 5,
- ),
- CustomButton(
- icon: AppAssets.view_report_icon,
- iconColor: AppColors.primaryRedColor,
- iconSize: 16.h,
- text: LocaleKeys.viewReport.tr(context: context),
- onPressed: () {},
- backgroundColor: AppColors.secondaryLightRedColor,
- borderColor: AppColors.secondaryLightRedColor,
- textColor: AppColors.primaryRedColor,
- fontSize: 14,
- fontWeight: FontWeight.bold,
- borderRadius: 12,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 40.h,
- ),
- ],
+ ),
+ CustomButton(
+ icon: AppAssets.view_report_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
+ text: LocaleKeys.viewReport.tr(context: context),
+ onPressed: () {
+ onTap();
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ borderRadius: 12,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
),
],
),
- )));
+ ],
+ ),
+ ));
}
}
diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart
index 6e910b8..8657534 100644
--- a/lib/presentation/lab/lab_orders_page.dart
+++ b/lib/presentation/lab/lab_orders_page.dart
@@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
@@ -30,6 +31,8 @@ class LabOrdersPage extends StatefulWidget {
class _LabOrdersPageState extends State {
late LabViewModel labProvider;
+ late LabRangeViewModel rangeViewModel;
+
List?> labSuggestions = [];
int? expandedIndex;
String? selectedFilterText = '';
@@ -45,6 +48,8 @@ class _LabOrdersPageState extends State {
@override
Widget build(BuildContext context) {
labProvider = Provider.of(context);
+ rangeViewModel = Provider.of(context);
+
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
@@ -154,9 +159,14 @@ class _LabOrdersPageState extends State {
child: FadeInAnimation(
child: LabOrderByTest(
onTap: () {
- setState(() {
- expandedIndex = isExpanded ? null : index;
- });
+ if(model.uniqueTests.toList()[index].model != null) {
+ rangeViewModel.flush();
+ model.getPatientLabResult(
+ model.uniqueTests
+ .toList()[index]
+ .model!, model.uniqueTests
+ .toList()[index].description!);
+ }
},
tests: model.uniqueTests.toList()[index],
index: index,
diff --git a/lib/presentation/lab/lab_results/lab_result_calender.dart b/lib/presentation/lab/lab_results/lab_result_calender.dart
new file mode 100644
index 0000000..b118293
--- /dev/null
+++ b/lib/presentation/lab/lab_results/lab_result_calender.dart
@@ -0,0 +1,336 @@
+import 'dart:async';
+
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/app_export.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart';
+import 'package:hmg_patient_app_new/features/lab/models/Range.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:provider/provider.dart';
+import 'package:syncfusion_flutter_calendar/calendar.dart';
+import 'package:syncfusion_flutter_datepicker/datepicker.dart';
+
+typedef OnRangeSelected = void Function(DateTime? start, DateTime? end);
+
+class LabResultCalender extends StatefulWidget {
+ final OnRangeSelected onRangeSelected;
+
+ const LabResultCalender({super.key, required this.onRangeSelected});
+
+ @override
+ State createState() => _LabResultCalenderState();
+}
+
+class _LabResultCalenderState extends State {
+ late DateRangePickerController _calendarController;
+ DateTime? start;
+ DateTime? end;
+ late LabRangeViewModel model;
+ @override
+ void initState() {
+ _calendarController = DateRangePickerController();
+ scheduleMicrotask(() {
+ _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate);
+ });
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ model = Provider.of(context);
+ return Padding(
+ padding: EdgeInsets.symmetric(horizontal: 0.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Consumer(
+ builder: (_, model, __) => selectionChip(model),
+ ).paddingOnly(bottom: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: false,
+ ),
+ padding: EdgeInsets.all(
+ 16.h
+ ),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ fromDateComponent(),
+ Text(
+ "to".needTranslation,
+ style: TextStyle(
+ color: AppColors.calenderTextColor,
+ fontSize: 14.h,
+ fontWeight: FontWeight.w500,
+ letterSpacing: -.2
+ ),
+ ).paddingSymmetrical(24.h,0.h),
+ toDateComponent(),
+ ],
+ ),
+ Divider(
+ color: AppColors.spacerLineColor,
+ thickness: 1,
+ ).paddingOnly(bottom: 16.h, top: 16.h),
+ Material(
+ color: Colors.white,
+ child: SfDateRangePicker(
+ controller: _calendarController,
+ selectionMode: DateRangePickerSelectionMode.range,
+ showNavigationArrow: true,
+ headerHeight: 40.h,
+ backgroundColor: Colors.white,
+ headerStyle: DateRangePickerHeaderStyle(
+ backgroundColor: Colors.white,
+ textAlign: TextAlign.start,
+ textStyle: TextStyle(
+ fontSize: 18.fSize,
+ fontWeight: FontWeight.w600,
+ letterSpacing: -0.46,
+ color: AppColors.primaryRedColor,
+ fontFamily: "Poppins",
+ ),
+ ),
+ monthViewSettings: DateRangePickerMonthViewSettings(
+ viewHeaderStyle: DateRangePickerViewHeaderStyle(
+ backgroundColor: Colors.white,
+ textStyle: TextStyle(
+ fontSize: 14.fSize,
+ fontWeight: FontWeight.w600,
+ letterSpacing: -0.46,
+ color: AppColors.textColor,
+ ),
+ ),
+ showTrailingAndLeadingDates: false,
+ dayFormat: "EEE",
+ ),
+ selectionShape: DateRangePickerSelectionShape.rectangle,
+ selectionRadius: 12.h,
+ selectionColor: AppColors.transparent,
+ startRangeSelectionColor: AppColors.primaryRedColor,
+ endRangeSelectionColor: AppColors.primaryRedColor,
+ rangeSelectionColor:
+ AppColors.primaryRedColor.withOpacity(0.1),
+ todayHighlightColor: Colors.transparent,
+ monthCellStyle: DateRangePickerMonthCellStyle(
+ textStyle: TextStyle(
+ fontSize: 12.fSize,
+ color: AppColors.textColor,
+ ),
+ todayTextStyle: TextStyle(
+ color: AppColors.textColor,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ onSelectionChanged:
+ (DateRangePickerSelectionChangedArgs args) {
+ if (args.value is PickerDateRange) {
+ final PickerDateRange range = args.value;
+ start = range.startDate;
+ end = range.endDate;
+ model.fromDate = start;
+ model.toDate = end;
+ model.resetCurrentlySelectedRange();
+ }
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ Row(
+ children: [
+ Consumer(
+ builder: (_, model, __) => Visibility(
+ visible: (model.fromDate != null || model.toDate != null),
+ child: Expanded(
+ child: Row(
+ children: [
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.cancel.tr(),
+ onPressed: () {
+ _calendarController.selectedRange = null;
+ _calendarController.selectedDate = null;
+ model.flush();
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ icon: AppAssets.cancel,
+ iconColor: AppColors.primaryRedColor,
+ height: 56.h,
+ ),
+ ),
+ SizedBox(width: 16.h,)
+ ],
+ ),
+ ),
+ ),
+ ),
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.search.tr(),
+ onPressed: () {
+ Navigator.of(context).pop();
+ widget.onRangeSelected(model.fromDate, model.toDate);
+ },
+ backgroundColor: AppColors.lightGreenButtonColor,
+ borderColor: Colors.transparent,
+ textColor: AppColors.textGreenColor,
+ icon: AppAssets.reminder_bell,
+ iconColor: AppColors.textGreenColor,
+
+ height: 56.h,
+ ),
+ ),
+ ],
+ ).paddingOnly(top: 24.h),
+ ],
+ ),
+ );
+ }
+
+ fromDateComponent() {
+ return Consumer(
+ builder: (_, model, __) {
+ return displayDate("Start Date".needTranslation,
+ model.getDateString(model.fromDate), model.fromDate == null);
+ },
+ );
+ }
+
+ toDateComponent() {
+ return Consumer(
+ builder: (_, model, __) {
+ return displayDate("End Date".needTranslation,
+ model.getDateString(model.toDate), model.toDate == null);
+ },
+ );
+ }
+
+ displayDate(String label, String? date, bool isNotSelected) => Expanded(
+ child: Row(
+ spacing: 12.h,
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.rangeCalendar,
+ iconColor: isNotSelected ? AppColors.borderOnlyColor: AppColors.blackColor ,
+ height: 24,
+ width: 24),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ label,
+ style: TextStyle(
+ color: AppColors.inputLabelTextColor,
+ fontSize: 12.h,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Text(
+ date!,
+ style: TextStyle(
+ color: AppColors.textColor,
+ fontSize: 14.h,
+ fontWeight: FontWeight.w500,
+ ),
+ )
+ ],
+ )
+ ],
+ ),
+ );
+
+ selectionChip(LabRangeViewModel model) {
+ return Row(
+ spacing: 8.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: "This Week".needTranslation,
+ backgroundColor: model.currentlySelectedRange == Range.WEEKLY
+ ? AppColors.primaryRedColor.withOpacity(0.1)
+ : AppColors.whiteColor,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: model.currentlySelectedRange == Range.WEEKLY
+ ? AppColors.primaryRedBorderColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ ).onPress((){
+ _calendarController.selectedRange = null;
+ model.currentlySelectedRange = Range.WEEKLY;
+ model.calculateDatesFromRange();
+ }),
+ AppCustomChipWidget(
+ labelText: "Last Month".needTranslation,
+ backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH
+ ? AppColors.primaryRedColor.withOpacity(0.1)
+ : AppColors.whiteColor,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: model.currentlySelectedRange == Range.LAST_MONTH
+ ? AppColors.primaryRedBorderColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ ).onPress((){
+ _calendarController.selectedRange = null;
+ model.currentlySelectedRange = Range.LAST_MONTH;
+ model.calculateDatesFromRange();
+ }),
+ AppCustomChipWidget(
+ labelText: "Last 6 Months".needTranslation,
+ backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH
+ ? AppColors.primaryRedColor.withOpacity(0.1)
+ : AppColors.whiteColor,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: model.currentlySelectedRange == Range.LAST_6MONTH
+ ? AppColors.primaryRedBorderColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ ).onPress((){
+ _calendarController.selectedRange = null;
+ model.currentlySelectedRange = Range.LAST_6MONTH;
+ model.calculateDatesFromRange();
+ }),
+ AppCustomChipWidget(
+ labelText: "Year ${model.getCurrentYear}",
+ backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR
+ ? AppColors.primaryRedColor.withOpacity(0.1)
+ : AppColors.whiteColor,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: model.currentlySelectedRange == Range.THIS_YEAR
+ ? AppColors.primaryRedBorderColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ ).onPress((){
+ _calendarController.selectedRange = null;
+ model.currentlySelectedRange = Range.THIS_YEAR;
+ model.calculateDatesFromRange();
+ }),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart
new file mode 100644
index 0000000..402e30e
--- /dev/null
+++ b/lib/presentation/lab/lab_results/lab_result_details.dart
@@ -0,0 +1,290 @@
+import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart';
+import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart' show LabRangeViewModel;
+import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
+import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
+import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
+import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart';
+import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
+import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
+import 'package:provider/provider.dart' show Consumer, Provider;
+
+import '../../../widgets/common_bottom_sheet.dart'
+ show showCommonBottomSheetWithoutHeight;
+import '../../book_appointment/widgets/appointment_calendar.dart'
+ show AppointmentCalendar;
+
+class LabResultDetails extends StatelessWidget {
+ final LabResult recentLabResult;
+
+ // final List graphPoint;
+ late LabViewModel model;
+
+ LabResultDetails({super.key, required this.recentLabResult});
+
+ @override
+ Widget build(BuildContext context) {
+ model = Provider.of(context, listen: false);
+ return CollapsingListView(
+ title: 'Lab Result Details'.needTranslation,
+ child: SingleChildScrollView(
+ child: Column(
+ spacing: 16.h,
+ children: [LabNameAndStatus, LabGraph(context)],
+ ).paddingAll(24.h),
+ ),
+ );
+ }
+
+ Widget get LabNameAndStatus => Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.h,
+ hasShadow: true,
+ ),
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 8.h,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ recentLabResult.testCode ?? "",
+ style: TextStyle(
+ fontSize: 32,
+ fontWeight: FontWeight.w600,
+ color: AppColors.textColor,
+ letterSpacing: -2),
+ ),
+ Text(
+ "Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation,
+ style: TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ],
+ ),
+ //todo change the text color according to the provided test values
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ child: Row(
+ spacing: 4.h,
+
+ children: [
+ Flexible(
+ child: Text(
+ recentLabResult.resultValue ?? "",
+ style: TextStyle(
+ fontSize: 24.fSize,
+ fontWeight: FontWeight.w600,
+ color: model.getColor(
+ recentLabResult.calculatedResultFlag ?? "",
+ ),
+ letterSpacing: -2,
+ ),
+ overflow: TextOverflow.ellipsis, // prevent overflow
+ maxLines: 1,
+ softWrap: false,
+ ),
+ ),
+ Visibility(
+ visible: recentLabResult.referanceRange != null,
+ child: Text(
+ "(Reference range ${recentLabResult.referanceRange})".needTranslation,
+ style: TextStyle(
+ fontSize: 12.fSize,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ overflow: TextOverflow.ellipsis,
+ maxLines: 1,
+ softWrap: false,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.lab_result_indicator,
+ width: 21,
+ height: 23,
+ iconColor: model.getColor(
+ recentLabResult.calculatedResultFlag ?? "",
+ ),
+ ),
+ ],
+ )
+
+ ],
+ ));
+
+ Widget LabGraph(BuildContext context) => Consumer(
+ builder: (_, model, ___) => Consumer(
+ builder: (_, labmodel, ___) => Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.h,
+ hasShadow: true,
+ ),
+ height: 260.h,
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ //title and filter icon
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ color: AppColors.textColor,
+ ),
+ ),
+ Row(
+ spacing: 16.h,
+ children: [
+ //todo handle when the graph icon is being displayed
+ Utils.buildSvgWithAssets(
+ icon: model.isGraphVisible?AppAssets.ic_list:AppAssets.ic_graph,
+ width: 24,
+ height: 24)
+ .onPress(() {
+ model.alterGraphVisibility();
+ }),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.ic_date_filter,
+ width: 24,
+ height: 24)
+ .onPress(() {
+ showCommonBottomSheetWithoutHeight(
+ title: "Set The Date Range".needTranslation,
+ context,
+ child: LabResultCalender(
+ onRangeSelected: (start, end) {
+
+ // if (start != null) {
+ labmodel.getSelectedDateRange(start, end);
+ // }
+ },
+ ),
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ callBackFunc: () {},
+ );
+ }),
+ ],
+ )
+ ],
+ ).paddingOnly(bottom: model.isGraphVisible? 16.h :24.h),
+ historyBody(model, labmodel)
+ ],
+ )),
+ ));
+
+ Widget leftLabels(String value) {
+ return Text(
+ value,
+ style: TextStyle(
+ fontWeight: FontWeight.w300,
+ fontFamily: 'Poppins',
+ fontSize: 8.fSize,
+ color: AppColors.textColor,
+ ),
+ );
+ }
+
+ Widget buildBottomLabel(String label) {
+ return Padding(
+ padding: const EdgeInsets.only(top:8.0),
+ child: Text(
+ label,
+ style: TextStyle(
+ fontSize: 8.fSize,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ color: AppColors.labelTextColor),
+ ),
+ );
+ }
+
+ Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) {
+ if(model.isGraphVisible){
+ return CustomGraph(
+ dataPoints: labmodel.filteredGraphValues,
+ maxY: 100,
+ leftLabelFormatter: (value) {
+ switch (value.toInt()) {
+ case 20:
+ return leftLabels("Critical Low".needTranslation);
+ case 40:
+ return leftLabels("Low".needTranslation);
+ case 60:
+ return leftLabels("Normal".needTranslation);
+ case 80:
+ return leftLabels("High".needTranslation);
+ case 100:
+ return leftLabels(
+ "Critical High".needTranslation);
+ default:
+ return SizedBox.shrink();
+ }
+ },
+ bottomLabelFormatter: (value, data) {
+ if(data.isEmpty) return SizedBox.shrink();
+ if (value == 0) {
+ return buildBottomLabel(data[value.toInt()].label);
+ }
+ if (value == data.length - 1) {
+ return buildBottomLabel(data[value.toInt()].label);
+ }
+ if (value == ((data.length - 1) / 2)) {
+ return buildBottomLabel(data[value.toInt()].label);
+ }
+ return SizedBox.shrink();
+ },
+ scrollDirection: Axis.horizontal,
+ height: 180.h);
+ }else {
+ return labHistoryList(model, labmodel);
+ }
+ }
+
+ Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) {
+ return SizedBox(
+ height: 180.h,
+ child: ListView.separated(
+ padding: EdgeInsets.zero,
+ itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){
+ var data = labmodel.filteredGraphValues.reversed.toList()[index];
+ return LabHistoryItem(
+ dayNameAndDate: labmodel.getFormattedDate(data.time),
+ result: data.actualValue,
+ assetUrl: labmodel.getAssetUrlWRTResult(data.refernceValue),
+ shouldRotateIcon: labmodel.getRotationWRTResult(data.refernceValue),
+ );
+ },
+ separatorBuilder: (_, __) => Divider(
+ color: AppColors.spacerLineColor,
+ thickness: 1.h,
+ ).paddingOnly(top: 4.h, bottom: 4.h),
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/lab/lab_results/lab_result_list_item.dart b/lib/presentation/lab/lab_results/lab_result_list_item.dart
new file mode 100644
index 0000000..00b5ff2
--- /dev/null
+++ b/lib/presentation/lab/lab_results/lab_result_list_item.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart' ;
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:hmg_patient_app_new/core/app_export.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+class LabHistoryItem extends StatelessWidget{
+
+ final String dayNameAndDate;
+ final String result;
+ final String assetUrl;
+ final bool shouldRotateIcon;
+
+ const LabHistoryItem({super.key, required this.dayNameAndDate, required this.result, required this.assetUrl, this.shouldRotateIcon = false});
+
+ @override
+ Widget build(BuildContext context) => Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ dayNameAndDate,
+ style: TextStyle(
+ fontSize: 14.fSize,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'Poppins',
+ color: AppColors.labelTextColor
+ ),
+ ),
+ Text(
+ result,
+ style: TextStyle(
+ fontSize: 18.fSize,
+ fontWeight: FontWeight.w600,
+ fontFamily: 'Poppins',
+ color: AppColors.textColor
+ ),
+ )
+ ],
+ ),
+ Transform.flip(
+ flipY: shouldRotateIcon,
+ child: Utils.buildSvgWithAssets(icon: assetUrl,height: 18, width: 18)
+ ),
+ ],
+ );
+
+}
\ No newline at end of file
diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart
index 06079dc..4015249 100644
--- a/lib/theme/colors.dart
+++ b/lib/theme/colors.dart
@@ -69,4 +69,10 @@ static const Color quickLoginColor = Color(0xFF666666);
static const Color tooltipTextColor = Color(0xFF414D55);
static const Color graphGridColor = Color(0x4D18C273);
+static const Color criticalLowAndHigh = Color(0xFFED1C2B);
+static const Color highAndLow = Color(0xFFFFAF15);
+static const Color labelTextColor = Color(0xFF838383);
+static const Color calenderTextColor = Color(0xFFD0D0D0);
+static const Color lightGreenButtonColor = Color(0x2618C273);
+
}
diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart
index d66d281..ee246ad 100644
--- a/lib/widgets/graph/custom_graph.dart
+++ b/lib/widgets/graph/custom_graph.dart
@@ -43,6 +43,7 @@ class CustomGraph extends StatelessWidget {
final double height;
final double? maxY;
final double? maxX;
+ final double? minX;
final Color spotColor;
final Color graphColor;
final Color graphShadowColor;
@@ -52,7 +53,9 @@ class CustomGraph extends StatelessWidget {
final FontWeight? bottomLabelFontWeight;
///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart
- final Widget Function(double value) leftLabelFormatter;
+ final Widget Function(double) leftLabelFormatter;
+ final Widget Function(double , List) bottomLabelFormatter;
+
final Axis scrollDirection;
final bool showBottomTitleDates;
@@ -76,6 +79,8 @@ class CustomGraph extends StatelessWidget {
this.bottomLabelColor = AppColors.textColor,
this.bottomLabelFontWeight = FontWeight.w500,
this.bottomLabelSize,
+ required this.bottomLabelFormatter,
+ this.minX,
});
@override
@@ -94,117 +99,108 @@ class CustomGraph extends StatelessWidget {
child: SizedBox(
width: width,
height: height,
- child: Padding(
- padding: const EdgeInsets.only(top: 8.0, bottom: 8),
- child: LineChart(
- LineChartData(
- minY: 0,
- maxY:
- ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
- // minX: dataPoints.first.labelValue - 1,
- maxX: maxX,
- minX: -0.2,
- lineTouchData: LineTouchData(
- getTouchLineEnd: (_, __) => 0,
- getTouchedSpotIndicator: (barData, indicators) {
- // Only show custom marker for touched spot
- return indicators.map((int index) {
- return TouchedSpotIndicatorData(
- FlLine(color: Colors.transparent),
- FlDotData(
- show: true,
- getDotPainter: (spot, percent, barData, idx) {
- return FlDotCirclePainter(
- radius: 8,
- color: spotColor,
- strokeWidth: 2,
- strokeColor: Colors.white,
- );
- },
- ),
- );
+ child: LineChart(
+ LineChartData(
+ minY: 0,
+ maxY:
+ ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
+ // minX: dataPoints.first.labelValue - 1,
+ maxX: maxX,
+ minX: minX ??-0.2,
+ lineTouchData: LineTouchData(
+ getTouchLineEnd: (_, __) => 0,
+ getTouchedSpotIndicator: (barData, indicators) {
+ // Only show custom marker for touched spot
+ return indicators.map((int index) {
+ return TouchedSpotIndicatorData(
+ FlLine(color: Colors.transparent),
+ FlDotData(
+ show: true,
+ getDotPainter: (spot, percent, barData, idx) {
+ return FlDotCirclePainter(
+ radius: 8,
+ color: spotColor,
+ strokeWidth: 2,
+ strokeColor: Colors.white,
+ );
+ },
+ ),
+ );
+ }).toList();
+ },
+ enabled: true,
+ touchTooltipData: LineTouchTooltipData(
+ getTooltipColor: (_) => Colors.white,
+ getTooltipItems: (touchedSpots) {
+ if (touchedSpots.isEmpty) return [];
+ // Only show tooltip for the first touched spot, hide others
+ return touchedSpots.map((spot) {
+ if (spot == touchedSpots.first) {
+ final dataPoint = dataPoints[spot.x.toInt()];
+
+ return LineTooltipItem(
+ // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
+ '${dataPoint.actualValue} ${dataPoint.displayTime}',
+ TextStyle(
+ color: Colors.black,
+ fontSize: 12.fSize,
+ fontWeight: FontWeight.w500),
+ );
+ }
+ return null; // hides the rest
}).toList();
},
- enabled: true,
- touchTooltipData: LineTouchTooltipData(
- getTooltipColor: (_) => Colors.white,
- getTooltipItems: (touchedSpots) {
- if (touchedSpots.isEmpty) return [];
- // Only show tooltip for the first touched spot, hide others
- return touchedSpots.map((spot) {
- if (spot == touchedSpots.first) {
- final dataPoint = dataPoints[spot.x.toInt()];
-
- return LineTooltipItem(
- // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
- '${dataPoint.value} ',
- TextStyle(
- color: Colors.black,
- fontSize: 12.fSize,
- fontWeight: FontWeight.w500),
- );
- }
- return null; // hides the rest
- }).toList();
- },
- ),
),
- titlesData: FlTitlesData(
- leftTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- reservedSize: 77,
- interval: .1, // Let fl_chart handle it
- getTitlesWidget: (value, _) {
- return leftLabelFormatter(value);
- },
- ),
- ),
- bottomTitles: AxisTitles(
- axisNameSize: 60,
- sideTitles: SideTitles(
- showTitles: showBottomTitleDates,
- reservedSize: 50,
- getTitlesWidget: (value, _) {
- if ((value.toDouble() >= 0) &&
- (value.toDouble() < (maxX ?? dataPoints.length))) {
- var label = dataPoints[value.toInt()].label;
-
- return buildBottomLabel(label);
- }
- return const SizedBox.shrink();
- },
- interval: 1, // ensures 1:1 mapping with spots
- ),
+ ),
+ titlesData: FlTitlesData(
+ leftTitles: AxisTitles(
+ sideTitles: SideTitles(
+ showTitles: true,
+ reservedSize: 77,
+ interval: .1, // Let fl_chart handle it
+ getTitlesWidget: (value, _) {
+ return leftLabelFormatter(value);
+ },
),
- topTitles: AxisTitles(),
- rightTitles: AxisTitles(),
),
- borderData: FlBorderData(
- show: true,
- border: const Border(
- bottom: BorderSide.none,
- left: BorderSide(color: Colors.grey, width: .5),
- right: BorderSide.none,
- top: BorderSide.none,
+ bottomTitles: AxisTitles(
+ axisNameSize: 20,
+ sideTitles: SideTitles(
+ showTitles: showBottomTitleDates,
+ reservedSize: 20,
+ getTitlesWidget: (value, _) {
+ return bottomLabelFormatter(value, dataPoints, );
+ },
+ interval: 1, // ensures 1:1 mapping with spots
),
),
- lineBarsData: _buildColoredLineSegments(dataPoints),
- gridData: FlGridData(
- show: true,
- drawVerticalLine: false,
- horizontalInterval: 20,
- checkToShowHorizontalLine: (value) =>
- value >= 0 && value <= 100,
- getDrawingHorizontalLine: (value) {
- return FlLine(
- color: AppColors.graphGridColor,
- strokeWidth: 1,
- dashArray: [5, 5],
- );
- },
+ topTitles: AxisTitles(),
+ rightTitles: AxisTitles(),
+ ),
+ borderData: FlBorderData(
+ show: true,
+ border: const Border(
+ bottom: BorderSide.none,
+ left: BorderSide(color: Colors.grey, width: .5),
+ right: BorderSide.none,
+ top: BorderSide.none,
),
),
+ lineBarsData: _buildColoredLineSegments(dataPoints),
+ gridData: FlGridData(
+ show: true,
+ drawVerticalLine: false,
+ horizontalInterval: 20,
+ checkToShowHorizontalLine: (value) =>
+ value >= 0 && value <= 100,
+ getDrawingHorizontalLine: (value) {
+ return FlLine(
+ color: AppColors.graphGridColor,
+ strokeWidth: 1,
+ dashArray: [5, 5],
+ );
+ },
+ ),
),
),
));
@@ -259,29 +255,20 @@ class CustomGraph extends StatelessWidget {
// );
// }
- Widget buildBottomLabel(String label) {
- return Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- label,
- style: TextStyle(
- fontSize: bottomLabelSize ?? 8.fSize, color: bottomLabelColor),
- ),
- );
- }
+
}
-final List sampleData = [
- DataPoint(
- value: 20,
- label: 'Jan 2024',
- ),
- DataPoint(
- value: 36,
- label: 'Feb 2024',
- ),
- DataPoint(
- value: 80,
- label: 'This result',
- ),
-];
+// final List sampleData = [
+// DataPoint(
+// value: 20,
+// label: 'Jan 2024',
+// ),
+// DataPoint(
+// value: 36,
+// label: 'Feb 2024',
+// ),
+// DataPoint(
+// value: 80,
+// label: 'This result',
+// ),
+// ];
diff --git a/pubspec.yaml b/pubspec.yaml
index 0466933..adf737b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -84,6 +84,7 @@ dependencies:
location: ^8.0.1
gms_check: ^1.0.4
huawei_location: ^6.14.2+301
+ intl: ^0.20.2
dev_dependencies:
flutter_test:
@@ -119,8 +120,8 @@ flutter:
weight: 500
- asset: assets/fonts/poppins/Poppins-Regular.ttf
weight: 400
- # - asset: assets/fonts/poppins/Poppins-Light.ttf
- # weight: 300
+ - asset: assets/fonts/poppins/Poppins-Light.ttf
+ weight: 300
# - asset: assets/fonts/poppins/Poppins-ExtraLight.ttf
# weight: 200
# - asset: assets/fonts/poppins/Poppins-Thin.ttf