From ac15ab26f54f32391bc1b05c614f14dd5229a3a7 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 7 Dec 2025 16:35:59 +0300 Subject: [PATCH 1/3] graph changes for high and low --- lib/core/api_consts.dart | 2 +- lib/features/lab/lab_view_model.dart | 50 +++- .../patient_lab_orders_response_model.dart | 3 + lib/presentation/lab/alphabeticScroll.dart | 235 ++++++++++++++++++ lib/presentation/lab/lab_order_by_test.dart | 1 + lib/presentation/lab/lab_orders_page.dart | 2 +- .../lab/lab_results/lab_result_details.dart | 50 +++- lib/widgets/appbar/collapsing_toolbar.dart | 205 +++++++++++++++ lib/widgets/graph/custom_graph.dart | 19 +- pubspec.yaml | 2 + 10 files changed, 548 insertions(+), 21 deletions(-) create mode 100644 lib/presentation/lab/alphabeticScroll.dart create mode 100644 lib/widgets/appbar/collapsing_toolbar.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 2be40d0..417519f 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -862,7 +862,7 @@ class ApiConsts { // ************ static values for Api **************** - static final double appVersionID = 19.3; + static final double appVersionID = 50.3; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index bad4f89..27ba643 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:core'; import 'dart:math'; @@ -39,6 +40,13 @@ class LabViewModel extends ChangeNotifier { String labSpecialResult = ""; List labOrderTests = []; String patientLabResultReportPDFBase64 = ""; + String? flagForHighReferenceRange; + double highRefrenceValue = double.negativeInfinity; + double lowRefenceValue = double.infinity; + + double highTransformedReferenceValue = double.negativeInfinity; + double lowTransformedReferenceValue = double.infinity; + String? flagForLowReferenceRange; PatientLabOrdersResponseModel? currentlySelectedPatientOrder; @@ -67,6 +75,8 @@ class LabViewModel extends ChangeNotifier { List get labSuggestions => _labSuggestionsList; Set uniqueTests = {}; + List uniqueTestsList = []; + List indexedCharacterForUniqueTest = []; double maxY = 0.0; double maxX = double.infinity; @@ -195,6 +205,19 @@ class LabViewModel extends ChangeNotifier { createdOn: item.createdOn, model: item)) }; + var sortedResult = SplayTreeSet.from(uniqueTests, (a, b) => a.description?[0].toUpperCase().compareTo(b.description?[0] ?? "") ?? -1); + uniqueTestsList = uniqueTests.toList(); + uniqueTestsList.sort((a, b) { + return a.description!.toLowerCase().compareTo(b.description!.toLowerCase()); + }); + indexedCharacterForUniqueTest.clear(); + for (var test in uniqueTestsList) { + String label = test.description ?? ""; + if (label.isEmpty) continue; + if (indexedCharacterForUniqueTest.contains(label[0].toLowerCase())) continue; + indexedCharacterForUniqueTest.add(label[0].toLowerCase()); + } + for (var element in uniqueTests) { labOrderTests.add(element.description ?? ""); } @@ -313,7 +336,12 @@ class LabViewModel extends ChangeNotifier { var recentThree = sort(sortedResponse); mainLabResults = recentThree; - double counter = 1; + + double highRefrenceValue = double.negativeInfinity; + String? flagForHighReferenceRange; + double lowRefenceValue = double.infinity; + String? flagForLowReferenceRange; + recentThree.reversed.forEach((element) { try { var dateTime = @@ -324,6 +352,14 @@ class LabViewModel extends ChangeNotifier { maxY = resultValue; maxX = maxY; } + if (highRefrenceValue < double.parse(element.referenceHigh ?? "0.0")) { + highRefrenceValue = double.parse(element.referenceHigh ?? "0.0"); + flagForHighReferenceRange = element.calculatedResultFlag; + } + if (lowRefenceValue > double.parse(element.referenceLow ?? "0.0")) { + lowRefenceValue = double.parse(element.referenceLow ?? "0.0"); + flagForLowReferenceRange = element.calculatedResultFlag; + } filteredGraphValues.add(DataPoint( value: transformedValue, @@ -335,9 +371,19 @@ class LabViewModel extends ChangeNotifier { referenceValue: element.calculatedResultFlag ?? "", )); - counter++; } catch (e) {} }); + maxY += transformValueInRange(50, "H"); + //todo handle the value if flags are null + if(flagForLowReferenceRange != null && flagForHighReferenceRange!= null){ + this.flagForHighReferenceRange = flagForHighReferenceRange; + this.flagForLowReferenceRange = flagForLowReferenceRange; + highTransformedReferenceValue = transformValueInRange(highRefrenceValue, flagForHighReferenceRange??""); + lowTransformedReferenceValue = transformValueInRange(lowRefenceValue, flagForLowReferenceRange??""); + this.highRefrenceValue = highRefrenceValue; + this.lowRefenceValue = lowRefenceValue; + } + LabResult recentResult = recentThree.first; checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.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 8bef76a..1c437a4 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 @@ -250,4 +250,7 @@ class TestDetails { data['CreatedOn'] = this.createdOn; return data; } + + @override + String toString() { return description??"";} } diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart new file mode 100644 index 0000000..3f82ba4 --- /dev/null +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -0,0 +1,235 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' show AnimationConfiguration, SlideAnimation, FadeInAnimation; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +import 'lab_order_by_test.dart'; + +class AlphabeticScroll extends StatefulWidget{ + final List alpahbetsAvailable; + final List details; + final AppState appState; + final LabViewModel labViewModel; + final DateRangeSelectorRangeViewModel rangeViewModel; + + const AlphabeticScroll({super.key, required this.alpahbetsAvailable, required this.details, required this.appState, required this.labViewModel, required this.rangeViewModel}); + + @override + State createState() => _AlphabetScrollPageState(); +} + +class _AlphabetScrollPageState extends State { + final ItemScrollController itemScrollController = ItemScrollController(); + final ScrollOffsetController scrollOffsetController = ScrollOffsetController(); + final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create(); + final ScrollOffsetListener scrollOffsetListener = ScrollOffsetListener.create(); + final ScrollController _scrollController = ScrollController(); + + Map> data = {}; + Map density = {}; + + Map _offsetMap = {}; + Map _offsetKeys = {}; + int _activeIndex = 0; // <-- Highlighted letter + + @override + void initState() { + super.initState(); + scheduleMicrotask((){ + for(var char in widget.alpahbetsAvailable){ + data[char] = widget.details.where((element)=>element.description?.toLowerCase().startsWith(char.toLowerCase()) == true).toList(); + + } + setState((){}); + + }); + itemPositionsListener.itemPositions.addListener((){ + + final positions = itemPositionsListener.itemPositions.value; + + if (positions.isEmpty) return; + + // Get FIRST visible item (top-most) + final firstVisible = positions + .where((p) => p.itemTrailingEdge > 0) // visible + .reduce((min, p) => + p.itemLeadingEdge < min.itemLeadingEdge ? p : min); + + if(_activeIndex == firstVisible.index) return ; + setState(() { + _activeIndex = firstVisible.index; + }); + + print("Active index = $_activeIndex"); + }); + } + + @override + void dispose() { + itemPositionsListener.itemPositions.removeListener((){ + + }); + super.dispose(); + + } + + void _scrollToLetter(String letter) async { + + // itemScrollController.jumpTo(index:density[letter]?.toInt()??0, ); + itemScrollController.scrollTo( + index: data.keys.toList().indexOf(letter).toInt()??0, + duration: Duration(seconds: 2), + curve: Curves.easeInOutCubic); + // final key = _offsetKeys[letter]; + // if (key == null) return; + // + // BuildContext? ctx = key.currentContext; + // + // // Retry until built (max 100ms) + // int retry = 0; + // while (ctx == null && retry < 5) { + // await Future.delayed(Duration(milliseconds: 20)); + // ctx = key.currentContext; + // retry++; + // } + // + // if (ctx == null) { + // print("❌ $letter still not built"); + // return; + // } + // + // final renderBox = ctx.findRenderObject() as RenderBox; + // final yOffset = renderBox.localToGlobal(Offset.zero).dy; + // + // _scrollController.animateTo( + // _scrollController.offset + yOffset - 80, + // duration: Duration(milliseconds: 400), + // curve: Curves.easeInOut, + // ); + } + + @override + Widget build(BuildContext context) { + + return + + SizedBox( + width: MediaQuery.sizeOf(context).width, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, // Add this + children: [ + Expanded( + child: Column( + children: [ + SizedBox( + height: (MediaQuery.sizeOf(context).height), + child: _buildList(widget.alpahbetsAvailable) + ), + ], + ), + ), + + SizedBox( + width: 24.w, + height: MediaQuery.sizeOf(context).height-(70.h+ kToolbarHeight+100.h), + child: Column( + mainAxisSize: MainAxisSize.max, // Add this + mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start + crossAxisAlignment: CrossAxisAlignment.center, + children:List.generate(widget.alpahbetsAvailable.length, (i) { + final isActive = (i == _activeIndex); + + return GestureDetector( + onTap: () { + setState(() => _activeIndex = i); + _scrollToLetter(widget.alpahbetsAvailable[i]); + }, + child: TweenAnimationBuilder( + tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0), + duration: Duration(milliseconds: 120), + curve: Curves.easeOut, + builder: (_, scale, child) { + return Transform.scale( + scale: scale, + child: Opacity( + opacity: isActive ? 1.0 : 0.5, + child: widget.alpahbetsAvailable[i].toUpperCase().toText14( + color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor + ), + ), + ); + }, + ), + ); + } + ), + ), + ), + ], + ), + ); + // ); + } + + Widget _buildList(List alphabet) { + return ScrollablePositionedList.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemScrollController: itemScrollController, + scrollOffsetController: scrollOffsetController, + itemPositionsListener: itemPositionsListener, + scrollOffsetListener: scrollOffsetListener, + itemCount: data.length, + itemBuilder: (_, index) { + final letter = alphabet[index].toLowerCase(); + print("the letter is $letter"); + final items = data[letter]!; + return Container( + key: _offsetKeys[letter], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + List.generate(items.length,(itemIndex)=> + AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: LabOrderByTest( + + appState: getIt(), + onTap: () { + if (items[itemIndex].model != null) { + widget.rangeViewModel.flush(); + widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!, + (widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!)); + } + }, + tests: items[itemIndex], + index: itemIndex, + isExpanded: true)), + ), + )) + // ...items.indexed((item) => + // + // + // + // ) + + ), + ); + }, + ); + } +} diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 2b791ae..bd56df6 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -27,6 +27,7 @@ class LabOrderByTest extends StatelessWidget { @override build(BuildContext context) { return AnimatedContainer( + key:key, duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 4ffd979..76b502a 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; 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/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/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'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; 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/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/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'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) // : ListView.builder( // shrinkWrap: true, // physics: NeverScrollableScrollPhysics(), // padding: EdgeInsets.zero, // itemCount: model.isLabOrdersLoading // ? 5 // : model.uniqueTests.toList().isNotEmpty // ? model.uniqueTests.toList().length // : 1, // itemBuilder: (context, index) { // final isExpanded = expandedIndex == index; // return model.isLabOrdersLoading // ? LabResultItemView( // onTap: () {}, // labOrder: null, // index: index, // isLoading: true, // ) // : model.uniqueTests.toList().isNotEmpty // ? AnimationConfiguration.staggeredList( // position: index, // duration: const Duration(milliseconds: 500), // child: SlideAnimation( // verticalOffset: 100.0, // child: FadeInAnimation( // child: LabOrderByTest( // appState: _appState, // onTap: () { // if (model.uniqueTests.toList()[index].model != null) { // rangeViewModel.flush(); // model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, // (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); // } // }, // tests: model.uniqueTests.toList()[index], // index: index, // isExpanded: isExpanded)), // ), // ) // : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); // }, // ) :AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 2330d0d..1cbad59 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -279,11 +280,37 @@ class LabResultDetails extends StatelessWidget { dataPoints: labmodel.filteredGraphValues, makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, + showGridLines: true, leftLabelInterval: getInterval(labmodel), - maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, + // maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, + maxY: (labmodel.maxY), maxX: labmodel.filteredGraphValues.length.toDouble()-.75, + horizontalInterval: .1, + getDrawingHorizontalLine: (value){ + print("the FLline transformed line is $value"); + print("the labmodel.highTransformedReferenceValue is ${labmodel.highTransformedReferenceValue}"); + print("thelabmodel.lowTransformedReferenceValue is ${labmodel.lowTransformedReferenceValue}"); + if(value == labmodel.highRefrenceValue ||value== labmodel.highRefrenceValue) { + return FlLine( + color: AppColors.greyTextColor, + strokeWidth: 1, + // dashArray: [5, 5], + ); + } + return FlLine( + color: Colors.transparent, + strokeWidth: 1, + // dashArray: [5, 5], + );; + }, leftLabelFormatter: (value) { - return leftLabels(value.toStringAsFixed(2)); + // return leftLabels(value.toStringAsFixed(2)); + if(value == labmodel.highTransformedReferenceValue) + return leftLabels("High".needTranslation); + + if(value== labmodel.lowTransformedReferenceValue) + return leftLabels("Low".needTranslation); + // switch (value.toInt()) { // case 10: // return leftLabels("Critical Low".needTranslation); @@ -297,7 +324,7 @@ class LabResultDetails extends StatelessWidget { // return leftLabels( // "Critical High".needTranslation); // default: - // return SizedBox.shrink(); + return SizedBox.shrink(); // } }, graphColor:graphColor , @@ -347,14 +374,15 @@ class LabResultDetails extends StatelessWidget { } double? getInterval(LabViewModel labmodel) { - var maxX = labmodel.maxY; - if(maxX<1) return .5; - 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; + return .1; + // var maxX = labmodel.maxY; + // if(maxX<1) return .5; + // 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; } Widget getLabDescription(BuildContext context) { diff --git a/lib/widgets/appbar/collapsing_toolbar.dart b/lib/widgets/appbar/collapsing_toolbar.dart new file mode 100644 index 0000000..87cf15a --- /dev/null +++ b/lib/widgets/appbar/collapsing_toolbar.dart @@ -0,0 +1,205 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.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/app_state.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/theme/colors.dart'; + +import '../../core/dependencies.dart'; + +class CollapsingToolbar extends StatefulWidget { + final String title; + Widget child; + VoidCallback? search; + VoidCallback? report; + VoidCallback? logout; + VoidCallback? history; + VoidCallback? instructions; + VoidCallback? requests; + Widget? bottomChild; + Widget? trailing; + bool isClose; + bool isLeading; + + CollapsingToolbar({ + super.key, + required this.title, + required this.child, + this.search, + this.isClose = false, + this.bottomChild, + this.report, + this.logout, + this.history, + this.instructions, + this.requests, + this.isLeading = true, + this.trailing, + }); + + @override + State createState() => _CollapsingToolbarState(); +} + +class _CollapsingToolbarState extends State { + bool isCollapsed = false; + + final ScrollController _controller = ScrollController(); + double expandedHeight = 0; + + double get maxCollapseOffset => expandedHeight - kToolbarHeight; + @override + void initState() { + super.initState(); + + + _controller.addListener(() { + // If scrolling UP beyond collapsed point → force stop + print("the height is $maxCollapseOffset"); + if (_controller.offset > maxCollapseOffset) { + _controller.jumpTo(maxCollapseOffset); + } + }); + } + + @override + Widget build(BuildContext context) { + expandedHeight = MediaQuery.of(context).size.height * 0.11.h; + AppState appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: + // Column( + // children: [ + NestedScrollView( + controller: _controller, + floatHeaderSlivers: true, + physics: isCollapsed?NeverScrollableScrollPhysics():BouncingScrollPhysics(), + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + expandedHeight: MediaQuery.of(context).size.height * 0.11.h, + stretch: true, + systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), + surfaceTintColor: Colors.transparent, + backgroundColor: AppColors.bgScaffoldColor, + leading: widget.isLeading + ? Transform.flip( + flipX: appState.isArabic(), + child: IconButton( + icon: Utils.buildSvgWithAssets(icon: widget.isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), + padding: EdgeInsets.only(left: 12), + onPressed: () => Navigator.pop(context), + highlightColor: Colors.transparent, + ), + ) + : SizedBox.shrink(), + flexibleSpace: LayoutBuilder( + builder: (context, constraints) { + final double maxHeight = 100.h; + final double minHeight = kToolbarHeight; + double t = (constraints.maxHeight - minHeight) / (maxHeight - minHeight); + t = t - 1; + if (t < 0.7) t = 0.7; + t = t.clamp(0.0, 1.0); + + final double fontSize = lerpDouble(14, 18, t)!; + final double bottomPadding = lerpDouble(0, 0, t)!; + final double leftPadding = lerpDouble(150, 24, t)!; + + return Stack( + children: [ + Align( + alignment: Alignment.lerp( + Alignment.center, + Alignment.bottomLeft, + t, + )!, + child: Padding( + padding: EdgeInsets.only(left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), + child: Row( + spacing: 4.h, + children: [ + Text( + widget.title, + maxLines: 1, + style: TextStyle( + fontSize: (27 - (5 * (2 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w300, + FontWeight.w600, + t, + )!, + color: AppColors.blackColor, + letterSpacing: -0.5), + ).expanded, + if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!).paddingOnly(right: 24), + if (widget.trailing != null) widget.trailing!, + ], + )), + ), + ], + ); + }, + ), + ), + ]; + }, + body: widget.child, + ), + + // ], + // ), + ); + } + + Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { + return AnimatedSize( + duration: Duration(milliseconds: 150), + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + margin: EdgeInsets.only(right: 24.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.secondaryLightRedColor, + borderRadius: 10.r, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), + if (t == 1) + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (14 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w300, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index 7fc1f2a..afa3c55 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -56,6 +56,9 @@ class CustomGraph extends StatelessWidget { final FontWeight? bottomLabelFontWeight; final double? leftLabelInterval; final double? leftLabelReservedSize; + final bool? showGridLines; + final GetDrawingGridLine? getDrawingHorizontalLine; + final double? horizontalInterval; ///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; @@ -89,13 +92,17 @@ class CustomGraph extends StatelessWidget { this.leftLabelReservedSize, this.makeGraphBasedOnActualValue = false, required this.bottomLabelFormatter, - this.minX, + this.minX, + this.showGridLines = false, + this.getDrawingHorizontalLine, + this.horizontalInterval, }); @override Widget build(BuildContext context) { // var maxY = 0.0; double interval = 20; + print("the maxY is $maxY"); return Material( color: Colors.white, @@ -190,12 +197,12 @@ class CustomGraph extends StatelessWidget { ), lineBarsData: _buildColoredLineSegments(dataPoints), gridData: FlGridData( - show: true, + show: showGridLines??true, drawVerticalLine: false, - // horizontalInterval: 40, - checkToShowHorizontalLine: (value) => - value >= 0 && value <= 100, - getDrawingHorizontalLine: (value) { + horizontalInterval:horizontalInterval, + // checkToShowHorizontalLine: (value) => + // value >= 0 && value <= 100, + getDrawingHorizontalLine: getDrawingHorizontalLine??(value) { return FlLine( color: graphGridColor, strokeWidth: 1, diff --git a/pubspec.yaml b/pubspec.yaml index 23e3ea9..79f5ca7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,6 +91,8 @@ dependencies: url: https://github.com/fleoparra/hms-flutter-plugin.git path: flutter-hms-map + scrollable_positioned_list: ^0.3.8 + dev_dependencies: flutter_test: sdk: flutter From 95243c250f40ed1d430ac5306ee3c5e9bb222b06 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Wed, 10 Dec 2025 10:18:09 +0300 Subject: [PATCH 2/3] graph shaded region added from horizontal spaces --- lib/features/lab/lab_view_model.dart | 31 ++++++---- .../lab/lab_results/lab_result_details.dart | 58 +++++++++++-------- lib/widgets/graph/custom_graph.dart | 55 +++++------------- 3 files changed, 70 insertions(+), 74 deletions(-) diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 27ba643..3997afb 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -79,6 +79,7 @@ class LabViewModel extends ChangeNotifier { List indexedCharacterForUniqueTest = []; double maxY = 0.0; + double minY = double.infinity; double maxX = double.infinity; LabViewModel( @@ -317,7 +318,8 @@ class LabViewModel extends ChangeNotifier { mainLabResults.clear(); filteredGraphValues.clear(); maxY = double.negativeInfinity; - + minY = double.infinity; + maxX = double.infinity; final result = await labRepo.getPatientLabResults( laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), @@ -352,6 +354,9 @@ class LabViewModel extends ChangeNotifier { maxY = resultValue; maxX = maxY; } + if (resultValue < minY) { + minY = resultValue; + } if (highRefrenceValue < double.parse(element.referenceHigh ?? "0.0")) { highRefrenceValue = double.parse(element.referenceHigh ?? "0.0"); flagForHighReferenceRange = element.calculatedResultFlag; @@ -373,17 +378,23 @@ class LabViewModel extends ChangeNotifier { )); } catch (e) {} }); - maxY += transformValueInRange(50, "H"); - //todo handle the value if flags are null - if(flagForLowReferenceRange != null && flagForHighReferenceRange!= null){ - this.flagForHighReferenceRange = flagForHighReferenceRange; - this.flagForLowReferenceRange = flagForLowReferenceRange; - highTransformedReferenceValue = transformValueInRange(highRefrenceValue, flagForHighReferenceRange??""); - lowTransformedReferenceValue = transformValueInRange(lowRefenceValue, flagForLowReferenceRange??""); - this.highRefrenceValue = highRefrenceValue; - this.lowRefenceValue = lowRefenceValue; + if (flagForLowReferenceRange == null && flagForHighReferenceRange == null) { + highRefrenceValue = maxY; + lowRefenceValue = minY; } + this.flagForHighReferenceRange = flagForHighReferenceRange; + this.flagForLowReferenceRange = flagForLowReferenceRange; + highTransformedReferenceValue = double.parse(transformValueInRange(highRefrenceValue, flagForHighReferenceRange ?? "").toStringAsFixed(1)); + lowTransformedReferenceValue = double.parse(transformValueInRange(lowRefenceValue, flagForLowReferenceRange ?? "").toStringAsFixed(1)); + this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(1)); + this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(1)); + + if(maxY< highRefrenceValue) { + maxY = highRefrenceValue; + } + maxY += 25; + minY -= 25; LabResult recentResult = recentThree.first; checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 1cbad59..d37dd72 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -284,15 +284,14 @@ class LabResultDetails extends StatelessWidget { leftLabelInterval: getInterval(labmodel), // maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, maxY: (labmodel.maxY), + minY: labmodel.minY, maxX: labmodel.filteredGraphValues.length.toDouble()-.75, horizontalInterval: .1, getDrawingHorizontalLine: (value){ - print("the FLline transformed line is $value"); - print("the labmodel.highTransformedReferenceValue is ${labmodel.highTransformedReferenceValue}"); - print("thelabmodel.lowTransformedReferenceValue is ${labmodel.lowTransformedReferenceValue}"); - if(value == labmodel.highRefrenceValue ||value== labmodel.highRefrenceValue) { + value = double.parse(value.toStringAsFixed(1)); + if(value == labmodel.highRefrenceValue ||value== labmodel.lowRefenceValue) { return FlLine( - color: AppColors.greyTextColor, + color: AppColors.bgGreenColor.withOpacity(0.6), strokeWidth: 1, // dashArray: [5, 5], ); @@ -300,35 +299,22 @@ class LabResultDetails extends StatelessWidget { return FlLine( color: Colors.transparent, strokeWidth: 1, - // dashArray: [5, 5], );; }, leftLabelFormatter: (value) { + value = double.parse(value.toStringAsFixed(1)); // return leftLabels(value.toStringAsFixed(2)); - if(value == labmodel.highTransformedReferenceValue) + if(value == labmodel.highRefrenceValue) return leftLabels("High".needTranslation); - if(value== labmodel.lowTransformedReferenceValue) + if(value== labmodel.lowRefenceValue) return leftLabels("Low".needTranslation); - // 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(.1), + graphColor:AppColors.blackColor, + graphShadowColor: Colors.transparent, graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { if(data.isEmpty) return SizedBox.shrink(); @@ -343,6 +329,9 @@ class LabResultDetails extends StatelessWidget { } return SizedBox.shrink(); }, + rangeAnnotations: RangeAnnotations( + horizontalRangeAnnotations: _buildRangeShades(labmodel) + ), minX:(labmodel.filteredGraphValues.length == 1)?null : -.2, scrollDirection: Axis.horizontal, height: 180.h); @@ -351,6 +340,29 @@ class LabResultDetails extends StatelessWidget { } } + List _buildRangeShades( LabViewModel model,) { + List ranges = []; + + ranges.add(HorizontalRangeAnnotation( + y1:model.minY, + y2: model.lowRefenceValue, + color: AppColors.highAndLow.withOpacity(0.05), + )); + + ranges.add(HorizontalRangeAnnotation( + y1:model.lowRefenceValue, + y2: model.highRefrenceValue, + color: AppColors.bgGreenColor.withOpacity(0.05), + )); + + ranges.add(HorizontalRangeAnnotation( + y1:model.highRefrenceValue, + y2: model.maxY, + color: AppColors.criticalLowAndHigh.withOpacity(0.05), + )); + return ranges; + } + Widget labHistoryList(LabViewModel labmodel) { return SizedBox( height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index afa3c55..b955b32 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -59,6 +59,9 @@ class CustomGraph extends StatelessWidget { final bool? showGridLines; final GetDrawingGridLine? getDrawingHorizontalLine; final double? horizontalInterval; + final double? minY; + final bool showShadow; + final RangeAnnotations? rangeAnnotations; ///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; @@ -96,14 +99,13 @@ class CustomGraph extends StatelessWidget { this.showGridLines = false, this.getDrawingHorizontalLine, this.horizontalInterval, + this.minY, + this.showShadow = false, + this.rangeAnnotations }); @override Widget build(BuildContext context) { - // var maxY = 0.0; - double interval = 20; - print("the maxY is $maxY"); - return Material( color: Colors.white, child: SizedBox( @@ -111,10 +113,8 @@ class CustomGraph extends StatelessWidget { height: height, child: LineChart( LineChartData( - minY: 0, - // maxY: ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), + minY: minY??0, maxY: maxY, - // minX: dataPoints.first.labelValue - 1, maxX: maxX, minX: minX , lineTouchData: LineTouchData( @@ -210,15 +210,17 @@ class CustomGraph extends StatelessWidget { ); }, ), + rangeAnnotations: rangeAnnotations ), ), - )); + ), + ); } + List _buildColoredLineSegments(List dataPoints) { final List allSpots = dataPoints.asMap().entries.map((entry) { 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(); @@ -228,7 +230,7 @@ class CustomGraph extends StatelessWidget { isCurved: true, isStrokeCapRound: true, isStrokeJoinRound: true, - barWidth: 4, + barWidth: 2, gradient: LinearGradient( colors: [graphColor, graphColor], begin: Alignment.centerLeft, @@ -238,7 +240,7 @@ class CustomGraph extends StatelessWidget { show: false, ), belowBarData: BarAreaData( - show: true, + show: showShadow, gradient: LinearGradient( colors: [ graphShadowColor, @@ -253,33 +255,4 @@ class CustomGraph extends StatelessWidget { return data; } - - // Widget buildLabel(String label) { - // return Padding( - // padding: const EdgeInsets.only(right: 8), - // child: Text( - // label, - // style: TextStyle( - // fontSize: leftLabelSize ?? 8.fSize, color: leftLabelColor), - // textAlign: TextAlign.right, - // ), - // ); - // } - - -} - -// final List sampleData = [ -// DataPoint( -// value: 20, -// label: 'Jan 2024', -// ), -// DataPoint( -// value: 36, -// label: 'Feb 2024', -// ), -// DataPoint( -// value: 80, -// label: 'This result', -// ), -// ]; +} \ No newline at end of file From 2d2380851eaf3f2a9b7723896708e775ebbf294c Mon Sep 17 00:00:00 2001 From: tahaalam Date: Wed, 10 Dec 2025 12:39:25 +0300 Subject: [PATCH 3/3] loading view added --- lib/presentation/lab/alphabeticScroll.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart index 3f82ba4..7593ea1 100644 --- a/lib/presentation/lab/alphabeticScroll.dart +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -213,7 +213,7 @@ class _AlphabetScrollPageState extends State { if (items[itemIndex].model != null) { widget.rangeViewModel.flush(); widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!, - (widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!)); + (widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!),""); } }, tests: items[itemIndex], diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 76b502a..a868d59 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; 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/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/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'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) // : ListView.builder( // shrinkWrap: true, // physics: NeverScrollableScrollPhysics(), // padding: EdgeInsets.zero, // itemCount: model.isLabOrdersLoading // ? 5 // : model.uniqueTests.toList().isNotEmpty // ? model.uniqueTests.toList().length // : 1, // itemBuilder: (context, index) { // final isExpanded = expandedIndex == index; // return model.isLabOrdersLoading // ? LabResultItemView( // onTap: () {}, // labOrder: null, // index: index, // isLoading: true, // ) // : model.uniqueTests.toList().isNotEmpty // ? AnimationConfiguration.staggeredList( // position: index, // duration: const Duration(milliseconds: 500), // child: SlideAnimation( // verticalOffset: 100.0, // child: FadeInAnimation( // child: LabOrderByTest( // appState: _appState, // onTap: () { // if (model.uniqueTests.toList()[index].model != null) { // rangeViewModel.flush(); // model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, // (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); // } // }, // tests: model.uniqueTests.toList()[index], // index: index, // isExpanded: isExpanded)), // ), // ) // : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); // }, // ) :AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; 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/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/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'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) // : ListView.builder( // shrinkWrap: true, // physics: NeverScrollableScrollPhysics(), // padding: EdgeInsets.zero, // itemCount: model.isLabOrdersLoading // ? 5 // : model.uniqueTests.toList().isNotEmpty // ? model.uniqueTests.toList().length // : 1, // itemBuilder: (context, index) { // final isExpanded = expandedIndex == index; // return model.isLabOrdersLoading // ? LabResultItemView( // onTap: () {}, // labOrder: null, // index: index, // isLoading: true, // ) // : model.uniqueTests.toList().isNotEmpty // ? AnimationConfiguration.staggeredList( // position: index, // duration: const Duration(milliseconds: 500), // child: SlideAnimation( // verticalOffset: 100.0, // child: FadeInAnimation( // child: LabOrderByTest( // appState: _appState, // onTap: () { // if (model.uniqueTests.toList()[index].model != null) { // rangeViewModel.flush(); // model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, // (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); // } // }, // tests: model.uniqueTests.toList()[index], // index: index, // isExpanded: isExpanded)), // ), // ) // : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); // }, // ) : (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) :AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file