Merge branch 'master' into haroon_dev

pull/67/head
haroon amjad 1 month ago
commit 6565ba719d

@ -6,7 +6,7 @@ class DataPoint {
final double value;
///label shown on the bottom of the graph
String label;
String refernceValue;
String referenceValue;
String actualValue;
DateTime time;
String displayTime;
@ -14,7 +14,7 @@ class DataPoint {
DataPoint(
{required this.value,
required this.label,
required this.refernceValue,
required this.referenceValue,
required this.actualValue,
required this.time,
required this.displayTime,

@ -1,4 +1,5 @@
import 'dart:core';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -52,7 +53,8 @@ class LabViewModel extends ChangeNotifier {
Set<TestDetails> uniqueTests = {};
double maxYForThreeDots = 0.0;
double maxY = 0.0;
double maxX = double.infinity;
LabViewModel(
{required this.labRepo,
@ -141,6 +143,7 @@ class LabViewModel extends ChangeNotifier {
LoaderBottomSheet.showLoader();
mainLabResults.clear();
filteredGraphValues.clear();
maxY = double.negativeInfinity;
final result = await labRepo.getPatientLabResults(
laborder,
@ -165,16 +168,20 @@ class LabViewModel extends ChangeNotifier {
try {
var dateTime =
DateUtil.convertStringToDate(element.verifiedOnDateTime!);
if (double.parse(element.resultValue!) > maxYForThreeDots) {
maxYForThreeDots = double.parse(element.resultValue!);
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
if (resultValue>maxY) {
maxY = resultValue;
maxX = maxY;
}
filteredGraphValues.add(DataPoint(
value: transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""),
value: transformedValue,
actualValue:element.resultValue!,
label: formatDateAsMMYY(dateTime),
displayTime: resultDate(dateTime),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
refernceValue: element.calculatedResultFlag ?? "",
referenceValue: element.calculatedResultFlag ?? "",
));
counter++;
@ -182,6 +189,7 @@ class LabViewModel extends ChangeNotifier {
});
LabResult recentResult = recentThree.first;
recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!));
// filteredGraphValues = [filteredGraphValues.first];
navigationService.push(MaterialPageRoute(
builder: (_) =>
LabResultDetails(recentLabResult: recentResult)));
@ -233,24 +241,32 @@ class LabViewModel extends ChangeNotifier {
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));
final transformedValue = rangeStart + ((normalizedValue * (rangeEnd - rangeStart)));
debugPrint("the actual value is $inputValue");
debugPrint("the flag is $flag");
debugPrint("the transformed value is $transformedValue");
return transformedValue;
}
void getSelectedDateRange(DateTime? start, DateTime? end) {
maxY = double.negativeInfinity;
if(start == null && end == null) {
print("the dates are null");
mainLabResults.forEach((element) {
final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
try{
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
if (resultValue > maxY) {
maxY = resultValue;
}
filteredGraphValues.add(DataPoint(
value: transformValueInRange(double.parse(element.resultValue!),
element.calculatedResultFlag ?? ""),
value: transformedValue,
actualValue: element.resultValue!,
label: formatDateAsMMYY(time),
displayTime: resultDate(time),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
refernceValue: element.calculatedResultFlag ?? "",
referenceValue: element.calculatedResultFlag ?? "",
));
}catch(e){
@ -264,33 +280,34 @@ class LabViewModel extends ChangeNotifier {
try {
var dateTime =
DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
if (resultValue > maxY) {
maxY = resultValue;
}
if (start != null && end == null) {
if (dateTime.isAtSameMomentAs(start)) {
filteredGraphValues.add(DataPoint(
value: transformValueInRange(
double.parse(element.resultValue!),
element.calculatedResultFlag ?? ""),
value: transformedValue,
actualValue: element.resultValue!,
label: formatDateAsMMYY(dateTime),
displayTime: resultDate(dateTime),
time:
DateUtil.convertStringToDate(element.verifiedOnDateTime),
refernceValue: element.calculatedResultFlag ?? ""));
referenceValue: 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 ?? ""),
value: transformedValue,
actualValue: element.resultValue!,
label: formatDateAsMMYY(dateTime),
displayTime: resultDate(dateTime),
time:
DateUtil.convertStringToDate(element.verifiedOnDateTime),
refernceValue: element.calculatedResultFlag ?? ""));
referenceValue: element.calculatedResultFlag ?? ""));
}
}
} catch (e) {}
@ -304,8 +321,7 @@ class LabViewModel extends ChangeNotifier {
String formatDateAsMMYY(DateTime date) {
String year = date.year.toString().substring(2);
return '${months[date.month-1]},$year';
return '${months[date.month-1]}, ${date.year}';
}

@ -1,20 +1,29 @@
import 'dart:math';
import 'package:dartz/dartz.dart';
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/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/appbar/collapsing_list_view.dart';
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 '../../../widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
import '../../book_appointment/widgets/appointment_calendar.dart'
show AppointmentCalendar;
class LabResultDetails extends StatelessWidget {
final LabResult recentLabResult;
@ -54,12 +63,18 @@ class LabResultDetails extends StatelessWidget {
children: [
Text(
recentLabResult.testCode ?? "",
style: TextStyle(fontSize: 32, fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2),
style: TextStyle(
fontSize: 32,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
letterSpacing: -2),
),
Text(
"Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation,
style: TextStyle(
fontSize: 12,
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
@ -73,6 +88,7 @@ class LabResultDetails extends StatelessWidget {
Expanded(
child: Row(
spacing: 4.h,
children: [
Flexible(
child: Text(
@ -80,6 +96,7 @@ class LabResultDetails extends StatelessWidget {
style: TextStyle(
fontSize: 24.fSize,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
color: model.getColor(
recentLabResult.calculatedResultFlag ?? "",
),
@ -97,6 +114,7 @@ class LabResultDetails extends StatelessWidget {
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
color: AppColors.greyTextColor,
),
overflow: TextOverflow.ellipsis,
@ -117,6 +135,7 @@ class LabResultDetails extends StatelessWidget {
),
],
)
],
));
@ -138,9 +157,11 @@ class LabResultDetails extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
model.isGraphVisible ? "History FlowChart".needTranslation : "History".needTranslation,
model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation,
style: TextStyle(
fontSize: 16,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
@ -149,15 +170,24 @@ class LabResultDetails extends StatelessWidget {
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(() {
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(() {
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);
// }
@ -171,7 +201,7 @@ class LabResultDetails extends StatelessWidget {
],
)
],
).paddingOnly(bottom: model.isGraphVisible ? 16.h : 24.h),
).paddingOnly(bottom: model.isGraphVisible? 16.h :24.h),
historyBody(model, labmodel)
],
)),
@ -181,7 +211,7 @@ class LabResultDetails extends StatelessWidget {
return Text(
value,
style: TextStyle(
fontWeight: FontWeight.w300,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontSize: 8.fSize,
color: AppColors.textColor,
@ -191,37 +221,52 @@ class LabResultDetails extends StatelessWidget {
Widget buildBottomLabel(String label) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
padding: const EdgeInsets.only(top:8.0),
child: Text(
label,
style: TextStyle(fontSize: 8.fSize, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor),
style: TextStyle(
fontSize: 8.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.labelTextColor),
),
);
}
Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) {
if (model.isGraphVisible) {
if(model.isGraphVisible){
var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N");
return CustomGraph(
dataPoints: labmodel.filteredGraphValues,
maxY: 100,
// maxY: 100,
makeGraphBasedOnActualValue: true,
leftLabelReservedSize: 40,
leftLabelInterval: getInterval(labmodel),
maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2,
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();
}
return leftLabels(value.toStringAsFixed(2).tr());
// switch (value.toInt()) {
// case 10:
// return leftLabels("Critical Low".needTranslation);
// case 30:
// return leftLabels("Low".needTranslation);
// case 50:
// return leftLabels("Normal".needTranslation);
// case 70:
// return leftLabels("High".needTranslation);
// case 90:
// return leftLabels(
// "Critical High".needTranslation);
// default:
// return SizedBox.shrink();
// }
},
graphColor:graphColor ,
graphShadowColor: graphColor.withOpacity(.4),
graphGridColor: graphColor.withOpacity(.4),
bottomLabelFormatter: (value, data) {
if (data.isEmpty) return SizedBox.shrink();
if(data.isEmpty) return SizedBox.shrink();
if (value == 0) {
return buildBottomLabel(data[value.toInt()].label);
}
@ -233,9 +278,10 @@ class LabResultDetails extends StatelessWidget {
}
return SizedBox.shrink();
},
minX:(labmodel.filteredGraphValues.length == 1)?null : -.2,
scrollDirection: Axis.horizontal,
height: 180.h);
} else {
}else {
return labHistoryList(model, labmodel);
}
}
@ -245,14 +291,13 @@ class LabResultDetails extends StatelessWidget {
height: 180.h,
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: labmodel.filteredGraphValues.length,
itemBuilder: (context, index) {
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),
assetUrl: labmodel.getAssetUrlWRTResult(data.referenceValue),
shouldRotateIcon: labmodel.getRotationWRTResult(data.referenceValue),
);
},
separatorBuilder: (_, __) => Divider(
@ -262,4 +307,14 @@ class LabResultDetails extends StatelessWidget {
),
);
}
double? getInterval(LabViewModel labmodel) {
var maxX = labmodel.maxY;
if(maxX >1 && maxX < 5) return 1;
if(maxX >5 && maxX < 10) return 5;
if(maxX >10 && maxX < 50) return 10;
if(maxX >50 && maxX < 100) return 20;
if(maxX >100 && maxX < 200) return 30;
return 50;
}
}

@ -3,40 +3,43 @@ import 'package:fl_chart/fl_chart.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/theme/colors.dart';
/// A customizable line graph widget using `fl_chart`.
///
/// CustomGraph(dataPoints: sampleData, scrollDirection: Axis.horizontal,height: 200,maxY: 100, maxX:2.5,
/// leftLabelFormatter: (value){
/// Widget buildLabel(String label) {
/// return Padding(
/// padding: const EdgeInsets.only(right: 8),
/// child: Text(
/// label,
/// style: TextStyle(
/// fontSize: 8.fSize, color: AppColors.textColor,
/// fontFamily:
/// FontUtils.getFontFamilyForLanguage(false)
/// ),
/// textAlign: TextAlign.right,
/// ),
/// );
/// }
/// switch (value.toInt()) {
/// Displays a line chart with configurable axis labels, colors, and data points.
/// Useful for visualizing time series or other sequential data.
///
/// case 20:
/// return buildLabel("Critical Low");
/// case 40:
/// return buildLabel("Low");
/// case 60:
/// return buildLabel("Normal");
/// case 80:
/// return buildLabel("High");
/// case 100:
/// return buildLabel("Critical High");
/// }
/// return const SizedBox.shrink();
/// },
/// **Parameters:**
/// - [dataPoints]: List of `DataPoint` objects to plot.
/// - [leftLabelFormatter]: Function to build left axis labels.
/// - [bottomLabelFormatter]: Function to build bottom axis labels.
/// - [width]: Optional width of the chart.
/// - [height]: Required height of the chart.
/// - [maxY], [maxX], [minX]: Axis bounds.
/// - [spotColor]: Color of the touched spot marker.
/// - [graphColor]: Color of the line.
/// - [graphShadowColor]: Color of the area below the line.
/// - [graphGridColor]: Color of the grid lines.
/// - [bottomLabelColor]: Color of bottom axis labels.
/// - [bottomLabelSize]: Font size for bottom axis labels.
/// - [bottomLabelFontWeight]: Font weight for bottom axis labels.
/// - [leftLabelInterval]: Interval between left axis labels.
/// - [leftLabelReservedSize]: Reserved space for left axis labels.
/// - [scrollDirection]: Axis direction for scrolling.
/// - [showBottomTitleDates]: Whether to show bottom axis labels.
/// - [isFullScreeGraph]: Whether the graph is fullscreen.
/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting.
///
/// ),
/// Example usage:
/// ```dart
/// CustomGraph(
/// dataPoints: sampleData,
/// leftLabelFormatter: (value) => ...,
/// bottomLabelFormatter: (value, dataPoints) => ...,
/// height: 200,
/// scrollDirection: Axis.horizontal,
/// maxY: 100,
/// maxX: 2.5,
/// )
class CustomGraph extends StatelessWidget {
final List<DataPoint> dataPoints;
final double? width;
@ -51,6 +54,8 @@ class CustomGraph extends StatelessWidget {
final Color bottomLabelColor;
final double? bottomLabelSize;
final FontWeight? bottomLabelFontWeight;
final double? leftLabelInterval;
final double? leftLabelReservedSize;
///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) leftLabelFormatter;
@ -60,6 +65,7 @@ class CustomGraph extends StatelessWidget {
final Axis scrollDirection;
final bool showBottomTitleDates;
final bool isFullScreeGraph;
final bool makeGraphBasedOnActualValue;
const CustomGraph({
super.key,
@ -79,6 +85,9 @@ class CustomGraph extends StatelessWidget {
this.bottomLabelColor = AppColors.textColor,
this.bottomLabelFontWeight = FontWeight.w500,
this.bottomLabelSize,
this.leftLabelInterval,
this.leftLabelReservedSize,
this.makeGraphBasedOnActualValue = false,
required this.bottomLabelFormatter,
this.minX,
});
@ -87,13 +96,7 @@ class CustomGraph extends StatelessWidget {
Widget build(BuildContext context) {
// var maxY = 0.0;
double interval = 20;
if ((maxY ?? 0) > 10 && (maxY ?? 0) <= 20) {
interval = 2;
} else if ((maxY ?? 0) > 5 && (maxY ?? 0) <= 10) {
interval = 1;
} else if ((maxY ?? 0) >= 0 && (maxY ?? 0) <= 5) {
interval = .4;
}
return Material(
color: Colors.white,
child: SizedBox(
@ -102,11 +105,11 @@ class CustomGraph extends StatelessWidget {
child: LineChart(
LineChartData(
minY: 0,
maxY:
((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
// maxY: ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
maxY: maxY,
// minX: dataPoints.first.labelValue - 1,
maxX: maxX,
minX: minX ??-0.2,
minX: minX ,
lineTouchData: LineTouchData(
getTouchLineEnd: (_, __) => 0,
getTouchedSpotIndicator: (barData, indicators) {
@ -140,7 +143,7 @@ class CustomGraph extends StatelessWidget {
return LineTooltipItem(
// '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
'${dataPoint.actualValue} ${dataPoint.displayTime}',
'${dataPoint.value} - ${dataPoint.actualValue} - ${dataPoint.displayTime}',
TextStyle(
color: Colors.black,
fontSize: 12.fSize,
@ -156,8 +159,8 @@ class CustomGraph extends StatelessWidget {
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 77,
interval: .1, // Let fl_chart handle it
reservedSize: leftLabelReservedSize??80,
interval: leftLabelInterval ?? .1, // Let fl_chart handle it
getTitlesWidget: (value, _) {
return leftLabelFormatter(value);
},
@ -190,12 +193,12 @@ class CustomGraph extends StatelessWidget {
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 20,
// horizontalInterval: 40,
checkToShowHorizontalLine: (value) =>
value >= 0 && value <= 100,
getDrawingHorizontalLine: (value) {
return FlLine(
color: AppColors.graphGridColor,
color: graphGridColor,
strokeWidth: 1,
dashArray: [5, 5],
);
@ -208,7 +211,9 @@ class CustomGraph extends StatelessWidget {
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints) {
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
double value = (makeGraphBasedOnActualValue)?double.tryParse(entry.value.actualValue)??0.0:entry.value.value;
debugPrint("the value is $value");
return FlSpot(entry.key.toDouble(), value);
}).toList();
var data = [

Loading…
Cancel
Save