diff --git a/lib/widgets/data_display/medical/LabResult/laboratory_result_widget.dart b/lib/widgets/data_display/medical/LabResult/laboratory_result_widget.dart index 148cf935..41e0475a 100644 --- a/lib/widgets/data_display/medical/LabResult/laboratory_result_widget.dart +++ b/lib/widgets/data_display/medical/LabResult/laboratory_result_widget.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:diplomaticquarterapp/core/model/labs/patient_lab_orders.dart'; import 'package:diplomaticquarterapp/core/viewModels/medical/labs_view_model.dart'; import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; @@ -11,6 +13,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:provider/provider.dart'; +import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/newUI/labWidgets.dart'; import 'LabResultWidget.dart'; @@ -69,10 +72,11 @@ class _LaboratoryResultWidgetState extends State { ), SizedBox(height: 10), if (model.labResultLists.isNotEmpty) - Container( - child: Column( - children: [ - InkWell( + Column( + children: [ + Padding( + padding: EdgeInsets.only(left: 21, right: 21), + child: GestureDetector( onTap: () { setState( () { @@ -80,59 +84,84 @@ class _LaboratoryResultWidgetState extends State { }, ); }, - child: Container( - padding: EdgeInsets.only(left: 17, top: 12, right: 13, bottom: 12), - margin: EdgeInsets.only(left: 21, right: 21, top: 12), - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: Colors.white, - borderRadius: BorderRadius.all( - Radius.circular(10.0), - ), - boxShadow: [ - BoxShadow( - color: Color(0xff000000).withOpacity(.05), - //spreadRadius: 5, - blurRadius: 27, - offset: Offset(0, -3), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + TranslationBase.of(context).generalResult, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16), ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - TranslationBase.of(context).generalResult, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16), - ), - ), - Icon( - _isShowMoreGeneral ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, - color: Color(0xff575757), - size: 22, - ) - ], - ), - if (_isShowMoreGeneral) - ...List.generate( - model.labResultLists.length, - (index) => LabResultWidget( - patientLabOrder: widget.patientLabOrder, - filterName: model.labResultLists[index].filterName, - patientLabResultList: model.labResultLists[index].patientLabResultList, - ), - ) - ], - ), + ), + Icon( + _isShowMoreGeneral ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, + color: Color(0xff575757), + size: 22, + ) + ], ), ), - ], - ), + ), + ItemResultCardWidgetWithParams( + title: "LYMPHOCYTES", subTitle: "0.4", referenceRange: "0.6-4", percentage: 5, note: "", source: "source", buttonText: "Click", type: ResultTypes.criticalLow), + ItemResultCardWidgetWithParams( + title: "Creatinine", + subTitle: "60", + referenceRange: "64-104", + percentage: 20, + note: "Mayoclinic is the source of pediatrics value", + source: "source", + buttonText: "Click", + type: ResultTypes.low), + ItemResultCardWidgetWithParams( + title: "WBC", + subTitle: "5.96", + referenceRange: "4-11", + percentage: 45, + note: "Mayoclinic is the source of pediatrics value", + source: "source", + buttonText: "Click", + type: ResultTypes.normal), + ItemResultCardWidgetWithParams(title: "NEUT%", subTitle: "55", referenceRange: "10-50", percentage: 65, note: "", source: "source", buttonText: "Click", type: ResultTypes.high), + _isShowMoreGeneral + ? Container( + padding: EdgeInsets.only(left: 17, top: 12, right: 13, bottom: 12), + margin: EdgeInsets.only(left: 21, right: 21, top: 12), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(10.0), + ), + boxShadow: [ + BoxShadow( + color: Color(0xff000000).withOpacity(.05), + //spreadRadius: 5, + blurRadius: 27, + offset: Offset(0, -3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_isShowMoreGeneral) + ...List.generate(model.labResultLists.length, (index) { + print(widget.patientLabOrder); + print(model.labResultLists[index].filterName); + print(jsonEncode(model.labResultLists[0].patientLabResultList)); + return LabResultWidget( + patientLabOrder: widget.patientLabOrder, + filterName: model.labResultLists[index].filterName, + patientLabResultList: model.labResultLists[index].patientLabResultList, + ); + }) + ], + ), + ) + : SizedBox(), + ], ), InkWell( onTap: () { diff --git a/lib/widgets/data_display/medical/LabResult/newUI/labWidgets.dart b/lib/widgets/data_display/medical/LabResult/newUI/labWidgets.dart new file mode 100644 index 00000000..34944579 --- /dev/null +++ b/lib/widgets/data_display/medical/LabResult/newUI/labWidgets.dart @@ -0,0 +1,455 @@ +import 'dart:math'; + +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:flutter/material.dart'; + +enum ResultTypes { criticalLow, low, normal, high, criticalHigh } + +class ItemResultCardWidget extends StatelessWidget { + final Widget child; + + const ItemResultCardWidget({ + Key? key, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12), + child: child, + ), + ), + ], + ); + } +} + +class CustomResultProgressBar extends StatelessWidget { + final double percentage; + final String value; + final ResultTypes type; + + const CustomResultProgressBar({Key? key, required this.percentage, required this.value, required this.type}) : super(key: key); + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (context, constraints) { + final double totalWidth = constraints.maxWidth; + final double spacing = 2; + final double tooltipPosition = (totalWidth * (percentage / 100)).clamp(0, totalWidth - 50); + return Stack( + children: [ + Row( + children: [ + _buildResultBar( + flex: 3, + color: Color(0xFFDE7676), + title: "Critical Low", + ), + SizedBox( + width: spacing, + ), + _buildResultBar( + flex: 2, + color: Color(0xFFEAB157), + title: "Low", + ), + SizedBox( + width: spacing, + ), + _buildResultBar( + flex: 5, + color: Color(0xFF09AA28), + title: "Normal", + ), + SizedBox( + width: spacing, + ), + _buildResultBar( + flex: 2, + color: Color(0xFFEAB157), + title: "High", + ), + SizedBox( + width: spacing, + ), + _buildResultBar( + flex: 3, + color: Color(0xFFDE7676), + title: "Critical High", + ), + ], + ), + Positioned( + left: tooltipPosition, + top: 33, + child: Column( + children: [ + TextCloud( + text: "${value}", + color: type == ResultTypes.criticalLow || type == ResultTypes.criticalHigh + ? Color(0xFFDE7676) + : type == ResultTypes.low || type == ResultTypes.high + ? Color(0xFFEAB157) + : type == ResultTypes.normal + ? Color(0xFF09AA28) + : Color(0xFF09AA28), + width: 60, + height: 20, + padding: EdgeInsets.zero, + axisDirection: AxisDirection.up, + ), + ], + ), + ), + ], + ); + }); + } + + Widget _buildResultBar({ + required int flex, + required Color color, + required String title, + }) { + return Expanded( + flex: flex, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: const TextStyle(fontSize: 10, fontFamily: 'Poppins', letterSpacing: -0.24, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Container( + height: 5, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + ], + ), + ); + } +} + +class TextCloud extends StatelessWidget { + final String text; // write here box content + final Color color; // Set box Color + final EdgeInsets padding; // Set content padding + final double width; // Box width + final double height; // Box Height + final AxisDirection axisDirection; // Set triangle location up,left,right,down + final double locationOfArrow; // set between 0 and 1, If 0.5 is set triangle position will be centered + const TextCloud({ + super.key, + required this.text, + this.color = Colors.white, + this.padding = const EdgeInsets.all(10), + this.width = 200, + this.height = 100, + this.axisDirection = AxisDirection.down, + this.locationOfArrow = 0.5, + }); + + @override + Widget build(BuildContext context) { + Size arrowSize = const Size(12, 12); + return Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: width, + height: height, + padding: padding, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: Text( + text, + style: TextStyle(color: Colors.white), + )), + ), + Builder(builder: (context) { + double angle = 0; + switch (axisDirection) { + case AxisDirection.left: + angle = pi * -0.5; + break; + case AxisDirection.up: + angle = pi * -2; + break; + case AxisDirection.right: + angle = pi * 0.5; + break; + case AxisDirection.down: + angle = pi; + break; + default: + angle = 0; + } + return Positioned( + left: axisDirection == AxisDirection.left + ? -arrowSize.width + 5 + : (axisDirection == AxisDirection.up || axisDirection == AxisDirection.down ? width * locationOfArrow - arrowSize.width / 2 : null), + right: axisDirection == AxisDirection.right ? -arrowSize.width + 5 : null, + top: axisDirection == AxisDirection.up + ? -arrowSize.width + 5 + : (axisDirection == AxisDirection.right || axisDirection == AxisDirection.left ? height * locationOfArrow - arrowSize.width / 2 : null), + bottom: axisDirection == AxisDirection.down ? -arrowSize.width + 5 : null, + child: Transform.rotate( + angle: angle, + child: CustomPaint( + size: arrowSize, + painter: ArrowPaint(color: color), + ), + ), + ); + }) + ], + ); + } +} + +class ArrowPaint extends CustomPainter { + final Color color; + + ArrowPaint({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + Path path_0 = Path(); + path_0.moveTo(size.width * 0.5745375, size.height * 0.06573708); + path_0.lineTo(size.width * 0.9813667, size.height * 0.8794000); + path_0.cubicTo(size.width * 1.001950, size.height * 0.9205625, size.width * 0.9852625, size.height * 0.9706208, size.width * 0.9441000, size.height * 0.9912000); + path_0.cubicTo(size.width * 0.9325250, size.height * 0.9969875, size.width * 0.9197667, size.height, size.width * 0.9068292, size.height); + path_0.lineTo(size.width * 0.09316958, size.height); + path_0.cubicTo(size.width * 0.04714583, size.height, size.width * 0.009836208, size.height * 0.9626917, size.width * 0.009836208, size.height * 0.9166667); + path_0.cubicTo(size.width * 0.009836208, size.height * 0.9037292, size.width * 0.01284829, size.height * 0.8909708, size.width * 0.01863392, size.height * 0.8794000); + path_0.lineTo(size.width * 0.4254625, size.height * 0.06573708); + path_0.cubicTo(size.width * 0.4460458, size.height * 0.02457225, size.width * 0.4961042, size.height * 0.007886875, size.width * 0.5372667, size.height * 0.02846929); + path_0.cubicTo(size.width * 0.5533958, size.height * 0.03653296, size.width * 0.5664708, size.height * 0.04961000, size.width * 0.5745375, size.height * 0.06573708); + path_0.close(); + + Paint paint_0_fill = Paint()..style = PaintingStyle.fill; + paint_0_fill.color = color; + canvas.drawPath(path_0, paint_0_fill); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +class ItemResultCardWidgetWithParams extends StatelessWidget { + final String title; + final String subTitle; + final String note; + final String source; + final String referenceRange; + final double percentage; + final String buttonText; + final ResultTypes type; + final bool isArabic; + final Function()? onButtonPressed; + + const ItemResultCardWidgetWithParams({ + Key? key, + required this.title, + required this.subTitle, + required this.referenceRange, + required this.percentage, + required this.note, + required this.source, + required this.buttonText, + required this.type, + this.onButtonPressed, + this.isArabic = false, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final fontFamily = isArabic ? 'Cairo' : 'Poppins'; + + return Container( + margin: const EdgeInsets.only(left: 21, right: 21, top: 21), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border( + left: BorderSide( + color: type == ResultTypes.criticalLow || type == ResultTypes.criticalHigh + ? Color(0xFFDE7676) + : type == ResultTypes.low || type == ResultTypes.high + ? Color(0xFFEAB157) + : type == ResultTypes.normal + ? Color(0xFF09AA28) + : Color(0xFF09AA28), + width: MediaQuery.of(context).size.width * 0.015, + ), + ), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + // Title Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontFamily: fontFamily, + fontWeight: FontWeight.w600, + color: const Color(0xff2E303A), + letterSpacing: -0.64, + height: 16 / 25, + ), + ), + GestureDetector( + onTap: onButtonPressed, + child: Utils.tableColumnValueWithUnderLine(TranslationBase.of(context).viewFlowChart, isLast: true, isCapitable: false), + ), + ], + ), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ResultStatusWidget(type: type), + SizedBox(width: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + subTitle, + style: TextStyle(fontSize: 14, fontFamily: fontFamily, fontWeight: FontWeight.w600, color: const Color(0xff2E303A), letterSpacing: -0.56), + ), + Text( + "Reference Range \n $referenceRange", + style: TextStyle( + fontSize: 10, + fontFamily: fontFamily, + fontWeight: FontWeight.w600, + color: const Color(0xff2E303A), + letterSpacing: -0.4, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 10), + // Progress Bar + SizedBox( + height: 55, + child: CustomResultProgressBar( + percentage: percentage, + value: subTitle, + type: type, + ), + ), + const SizedBox(height: 12), + // Note Section + if (note.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(color: Color(0xFFEFEFEF), thickness: 1), + const SizedBox(height: 10), + Text( + "Notes:", + style: TextStyle( + fontSize: 12, + color: const Color(0xFF2E303A), + letterSpacing: -0.4, + fontWeight: FontWeight.bold, + ), + ), + Text( + note, + style: TextStyle( + fontSize: 12, + color: const Color(0xFF575757), + letterSpacing: -0.4, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 3), + ], + ), + ), + ); + } +} + +class ResultStatusWidget extends StatelessWidget { + final ResultTypes type; + + const ResultStatusWidget({ + Key? key, + required this.type, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Color? color; + IconData? icon; + + switch (type) { + case ResultTypes.criticalLow: + color = const Color(0xFFDE7676); + icon = Icons.arrow_circle_down; + break; + case ResultTypes.low: + color = const Color(0xFFEAB157); + icon = Icons.arrow_circle_down; + break; + case ResultTypes.criticalHigh: + color = const Color(0xFFDE7676); + icon = Icons.arrow_circle_up; + break; + case ResultTypes.high: + color = const Color(0xFFEAB157); + icon = Icons.arrow_circle_up; + break; + case ResultTypes.normal: + color = const Color(0xFF09AA28); + icon = Icons.check_circle; + break; + } + + // Return the icon if defined, otherwise a placeholder widget + return icon != null + ? Icon( + icon, + color: color, + ) + : const SizedBox(); // Use SizedBox to keep the layout consistent + } +}