From ac15ab26f54f32391bc1b05c614f14dd5229a3a7 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 7 Dec 2025 16:35:59 +0300 Subject: [PATCH 1/5] 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/5] 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/5] 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 From a83a35a2846852c9fc3aef9da3e7fad958e77e3c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Dec 2025 14:11:53 +0300 Subject: [PATCH 4/5] Services Page Revamp --- assets/images/svg/blood_donation_icon.svg | 4 + assets/images/svg/car_parking_icon.svg | 4 + assets/images/svg/e-referral.svg | 12 +- assets/images/svg/hmg_contact_icon.svg | 6 + assets/images/svg/latest_news_icon.svg | 3 + .../images/svg/services_medical_file_icon.svg | 8 + assets/images/svg/virtual_tour_icon.svg | 3 + lib/core/app_assets.dart | 6 + .../blood_donation_view_model.dart | 19 +- .../hmg_services_component_model.dart | 2 - lib/generated/locale_keys.g.dart | 2 +- .../blood_donation/blood_donation_page.dart | 47 +- .../widgets/select_blood_group_widget.dart | 67 +++ .../widgets/select_gender_widget.dart | 67 +++ .../hmg_services/services_page.dart | 430 +++++++++++++++--- .../hmg_services/services_view.dart | 26 +- lib/theme/colors.dart | 5 + 17 files changed, 617 insertions(+), 94 deletions(-) create mode 100644 assets/images/svg/blood_donation_icon.svg create mode 100644 assets/images/svg/car_parking_icon.svg create mode 100644 assets/images/svg/hmg_contact_icon.svg create mode 100644 assets/images/svg/latest_news_icon.svg create mode 100644 assets/images/svg/services_medical_file_icon.svg create mode 100644 assets/images/svg/virtual_tour_icon.svg create mode 100644 lib/presentation/blood_donation/widgets/select_blood_group_widget.dart create mode 100644 lib/presentation/blood_donation/widgets/select_gender_widget.dart diff --git a/assets/images/svg/blood_donation_icon.svg b/assets/images/svg/blood_donation_icon.svg new file mode 100644 index 0000000..2a90705 --- /dev/null +++ b/assets/images/svg/blood_donation_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/car_parking_icon.svg b/assets/images/svg/car_parking_icon.svg new file mode 100644 index 0000000..f77afa0 --- /dev/null +++ b/assets/images/svg/car_parking_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/e-referral.svg b/assets/images/svg/e-referral.svg index 3262779..758affc 100644 --- a/assets/images/svg/e-referral.svg +++ b/assets/images/svg/e-referral.svg @@ -1,7 +1,7 @@ - - - - - - + + + + + + diff --git a/assets/images/svg/hmg_contact_icon.svg b/assets/images/svg/hmg_contact_icon.svg new file mode 100644 index 0000000..c6c427b --- /dev/null +++ b/assets/images/svg/hmg_contact_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/latest_news_icon.svg b/assets/images/svg/latest_news_icon.svg new file mode 100644 index 0000000..6dbb4de --- /dev/null +++ b/assets/images/svg/latest_news_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/services_medical_file_icon.svg b/assets/images/svg/services_medical_file_icon.svg new file mode 100644 index 0000000..7286054 --- /dev/null +++ b/assets/images/svg/services_medical_file_icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/virtual_tour_icon.svg b/assets/images/svg/virtual_tour_icon.svg new file mode 100644 index 0000000..f7eb64f --- /dev/null +++ b/assets/images/svg/virtual_tour_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 95f24ca..1c17024 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -182,6 +182,12 @@ class AppAssets { static const String call_for_doctor = '$svgBasePath/call_for_doctor.svg'; static const String bulb_icon = '$svgBasePath/bulb_icon.svg'; static const String select_city_icon = '$svgBasePath/select_city_icon.svg'; + static const String blood_donation_icon = '$svgBasePath/blood_donation_icon.svg'; + static const String virtual_tour_icon = '$svgBasePath/virtual_tour_icon.svg'; + static const String car_parking_icon = '$svgBasePath/car_parking_icon.svg'; + static const String latest_news_icon = '$svgBasePath/latest_news_icon.svg'; + static const String hmg_contact_icon = '$svgBasePath/hmg_contact_icon.svg'; + static const String services_medical_file_icon = '$svgBasePath/services_medical_file_icon.svg'; //bottom navigation// diff --git a/lib/features/blood_donation/blood_donation_view_model.dart b/lib/features/blood_donation/blood_donation_view_model.dart index 7d2e5df..f345359 100644 --- a/lib/features/blood_donation/blood_donation_view_model.dart +++ b/lib/features/blood_donation/blood_donation_view_model.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_list_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -27,11 +29,16 @@ class BloodDonationViewModel extends ChangeNotifier { BloodGroupListModel("B-", 7), ]; + List genderList = [ + BloodGroupListModel(LocaleKeys.malE.tr(), 1), + BloodGroupListModel(LocaleKeys.female.tr(), 2), + ]; + late CitiesModel selectedCity; late BloodGroupListModel selectedBloodGroup; int _selectedHospitalIndex = 0; int _selectedBloodTypeIndex = 0; - String _selectedBloodType = ''; + String selectedBloodType = ''; List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel(); @@ -42,6 +49,12 @@ class BloodDonationViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedBloodGroup(BloodGroupListModel bloodGroup) { + selectedBloodGroup = bloodGroup; + selectedBloodType = selectedBloodGroup.name; + notifyListeners(); + } + Future getRegionSelectedClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { citiesList.clear(); selectedCity = CitiesModel(); @@ -85,8 +98,8 @@ class BloodDonationViewModel extends ChangeNotifier { citiesModel.description = citiesList[_selectedHospitalIndex].description; citiesModel.descriptionN = citiesList[_selectedHospitalIndex].descriptionN; selectedCity = citiesModel; - _selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!; - _selectedBloodTypeIndex = getBloodIndex(_selectedBloodType); + selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!; + _selectedBloodTypeIndex = getBloodIndex(selectedBloodType); notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart index d6e2654..7a92198 100644 --- a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -9,7 +9,6 @@ class HmgServicesComponentModel { bool isLogin; bool isLocked; Color bgColor; - Color textColor; String? route; Function? onTap; @@ -22,7 +21,6 @@ class HmgServicesComponentModel { this.isLogin, { this.isLocked = false, this.bgColor = Colors.white, - this.textColor = Colors.black, this.iconColor = Colors.white, this.route, this.onTap diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7433ff7..d76422b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -801,7 +801,7 @@ abstract class LocaleKeys { static const fullName = 'fullName'; static const married = 'married'; static const uae = 'uae'; - static const malE = 'malE'; + static const malE = 'male'; static const loginBy = 'loginBy'; static const loginByOTP = 'loginByOTP'; static const guest = 'guest'; diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index 8908652..5f3fa86 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -9,12 +9,13 @@ 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/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart'; +import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class BloodDonationPage extends StatelessWidget { @@ -69,19 +70,13 @@ class BloodDonationPage extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), ], ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Cities..."); - await bloodDonationVM.getRegionSelectedClinics(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, - title: LocaleKeys.selectCity.tr(context: context), - isDismissible: true, - child: SelectCityWidget( - bloodDonationViewModel: bloodDonationVM, - ), - callBackFunc: () {}); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - }); + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.selectCity.tr(context: context), + isDismissible: true, + child: SelectCityWidget( + bloodDonationViewModel: bloodDonationVM, + ), + callBackFunc: () {}); }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), @@ -104,7 +99,16 @@ class BloodDonationPage extends StatelessWidget { ), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), ], - ), + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.selectGender.tr(context: context), + isDismissible: true, + child: SelectGenderWidget( + isArabic: appState.isArabic(), + bloodDonationViewModel: bloodDonationVM, + ), + callBackFunc: () {}); + }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), @@ -119,14 +123,23 @@ class BloodDonationPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - "AB+".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + bloodDonationVM.selectedBloodType.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ], ), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), ], - ), + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.select.tr(context: context), + isDismissible: true, + child: SelectBloodGroupWidget( + isArabic: appState.isArabic(), + bloodDonationViewModel: bloodDonationVM, + ), + callBackFunc: () {}); + }), ], ), ), diff --git a/lib/presentation/blood_donation/widgets/select_blood_group_widget.dart b/lib/presentation/blood_donation/widgets/select_blood_group_widget.dart new file mode 100644 index 0000000..f9cadb2 --- /dev/null +++ b/lib/presentation/blood_donation/widgets/select_blood_group_widget.dart @@ -0,0 +1,67 @@ +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'; +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/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/city_list_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class SelectBloodGroupWidget extends StatelessWidget { + SelectBloodGroupWidget({super.key, required this.bloodDonationViewModel, required this.isArabic}); + + BloodDonationViewModel bloodDonationViewModel; + bool isArabic; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [bloodDonationViewModel.bloodGroupList[index].name.toText16(color: AppColors.textColor, isBold: true)], + ), + ), + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h).onPress(() { + bloodDonationViewModel.setSelectedBloodGroup(bloodDonationViewModel.bloodGroupList[index]); + Navigator.of(context).pop(); + })); + }, + separatorBuilder: (_, __) => SizedBox( + height: 8.h, + ), + itemCount: bloodDonationViewModel.bloodGroupList.length), + ) + ], + ); + } +} diff --git a/lib/presentation/blood_donation/widgets/select_gender_widget.dart b/lib/presentation/blood_donation/widgets/select_gender_widget.dart new file mode 100644 index 0000000..b253331 --- /dev/null +++ b/lib/presentation/blood_donation/widgets/select_gender_widget.dart @@ -0,0 +1,67 @@ +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'; +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/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/city_list_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class SelectGenderWidget extends StatelessWidget { + SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic}); + + BloodDonationViewModel bloodDonationViewModel; + bool isArabic; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [bloodDonationViewModel.genderList[index].name.toText16(color: AppColors.textColor, isBold: true)], + ), + ), + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h).onPress(() { + // bloodDonationViewModel.setSelectedCity(bloodDonationViewModel.citiesList[index]); + Navigator.of(context).pop(); + })); + }, + separatorBuilder: (_, __) => SizedBox( + height: 8.h, + ), + itemCount: bloodDonationViewModel.genderList.length), + ) + ], + ); + } +} diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 1b9f3cd..ab70265 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -1,94 +1,141 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/enums.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/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +import '../../core/dependencies.dart' show getIt; class ServicesPage extends StatelessWidget { ServicesPage({super.key}); - final List hmgServices = [ + late BloodDonationViewModel bloodDonationViewModel; + late MedicalFileViewModel medicalFileViewModel; + + late final List hmgServices = [ HmgServicesComponentModel( 11, - "E Referral Services".needTranslation, + "Emergency Services".needTranslation, "".needTranslation, - AppAssets.eReferral, + AppAssets.emergency_services_icon, + bgColor: AppColors.primaryRedColor, + true, + route: AppRoutes.eReferralPage, + ), + HmgServicesComponentModel( + 11, + "Book\nAppointment".needTranslation, + "".needTranslation, + AppAssets.appointment_calendar_icon, + bgColor: AppColors.bookAppointment, true, - bgColor: Colors.orange, - textColor: AppColors.blackColor, route: AppRoutes.eReferralPage, ), HmgServicesComponentModel( 5, - "Comprehensive Checkup".needTranslation, + "Complete Checkup".needTranslation, "".needTranslation, AppAssets.comprehensiveCheckup, - true, bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, + true, route: AppRoutes.comprehensiveCheckupPage, ), HmgServicesComponentModel( - 3, - "Home Health Care".needTranslation, + 11, + "E-Referral Services".needTranslation, "".needTranslation, - AppAssets.emergency_services_icon, + AppAssets.eReferral, + bgColor: AppColors.eReferralCardColor, true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: AppRoutes.homeHealthCarePage, + route: AppRoutes.eReferralPage, ), + HmgServicesComponentModel(3, "Blood Donation".needTranslation, "".needTranslation, AppAssets.blood_donation_icon, bgColor: AppColors.bloodDonationCardColor, true, route: null, onTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); + await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { + await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( + CustomPageRoute( + page: BloodDonationPage(), + ), + ); + }); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + }); + }), HmgServicesComponentModel( 3, - "Blood Donation".needTranslation, + "Home Health Care".needTranslation, "".needTranslation, - AppAssets.emergency_services_icon, + AppAssets.homeBottom, + bgColor: AppColors.primaryRedColor, true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: AppRoutes.bloodDonationPage, + route: AppRoutes.homeHealthCarePage, ), - HmgServicesComponentModel( - 11, - "Virtual Tour".needTranslation, - "".needTranslation, - AppAssets.my_address, - true, - bgColor: Colors.orange, - textColor: AppColors.blackColor, - route: null, - onTap:(){ - Utils.openWebView( - url: 'https://hmgwebservices.com/vt_mobile/html/index.html', - ); - }, - ) + // HmgServicesComponentModel( + // 11, + // "Virtual Tour".needTranslation, + // "".needTranslation, + // AppAssets.my_address, + // bgColor: AppColors.quickLoginColor, + // true, + // route: null, + // onTap:(){ + // Utils.openWebView( + // url: 'https://hmgwebservices.com/vt_mobile/html/index.html', + // ); + // }, + // ) ]; @override Widget build(BuildContext context) { - return CollapsingListView( - title: "Explore Services".needTranslation, - isLeading: Navigator.canPop(context), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Medical & Care Services".needTranslation.toText18(isBold: true), - SizedBox(height: 20.h), - Padding( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 0), - child: GridView.builder( + bloodDonationViewModel = Provider.of(context); + medicalFileViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Explore Services".needTranslation, + isLeading: false, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + "Medical & Care Services".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, // 4 icons per row - crossAxisSpacing: 24.w, - mainAxisSpacing: 0.h, - childAspectRatio: 0.85, + crossAxisCount: 4, // 4 icons per row + crossAxisSpacing: 12.w, + mainAxisSpacing: 18.h, + childAspectRatio: 0.8, ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -98,8 +145,285 @@ class ServicesPage extends StatelessWidget { return ServiceGridViewItem(hmgServices[index], index, false); }, ), - ) - ], + SizedBox(height: 24.h), + "Personal Services".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + height: 170.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 30.w, height: 30.h), + "Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + ], + ), + Spacer(), + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }), + Spacer(), + CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 16.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: "Recharge".needTranslation, + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 12.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + }), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + height: 170.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), + "Medical Files".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + ], + ), + Spacer(), + Wrap( + spacing: -8.h, + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 28.h, + width: 28.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 28.h, + width: 28.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.male_img, + height: 28.h, + width: 28.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], + ), + Spacer(), + CustomButton( + height: 40.h, + icon: AppAssets.add_icon, + iconSize: 16.w, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: "Add Member".needTranslation, + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 12.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: "Add Family Member".needTranslation, + message: "Please fill the below field to add a new family member to your profile".needTranslation, + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + ), + ], + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + }), + ), + ), + ], + ), + SizedBox(height: 24.h), + "Support Services".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.virtual_tour_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Virtual Tour".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ), + ), + ).onPress(() { + Utils.openWebView( + url: 'https://hmgwebservices.com/vt_mobile/html/index.html', + ); + }), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.car_parking_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ), + ), + ), + ), + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.latest_news_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Latest News".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ), + ), + ).onPress(() { + Utils.openWebView( + url: 'https://x.com/HMG', + ); + }), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.hmg_contact_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "HMG Contact".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + ) + ], + ) + ], + ), + SizedBox(height: 24.h), + ], + ), ), ), ); diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 2cd923f..b24b0b4 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -3,8 +3,10 @@ import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/dependencies.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/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class ServiceGridViewItem extends StatelessWidget { final HmgServicesComponentModel hmgServiceComponentModel; @@ -31,25 +33,25 @@ class ServiceGridViewItem extends StatelessWidget { Container( height: 48.h, width: 48.w, - padding: EdgeInsets.zero, - margin: EdgeInsets.zero, - decoration: BoxDecoration( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: hmgServiceComponentModel.bgColor, - borderRadius: BorderRadius.circular(12.r), + borderRadius: 12.r, + hasShadow: false, ), - child: Utils.buildSvgWithAssets( - icon: hmgServiceComponentModel.icon, - iconColor: hmgServiceComponentModel.iconColor, - height: 21.h, - width: 21.w, - fit: BoxFit.none, + child: Padding( + padding: EdgeInsets.all(12.h), + child: Utils.buildSvgWithAssets( + icon: hmgServiceComponentModel.icon, + iconColor: AppColors.whiteColor, + fit: BoxFit.contain, + ), ), ), SizedBox(height: 5.h), hmgServiceComponentModel.title.toText12( fontWeight: FontWeight.w500, - color: hmgServiceComponentModel.textColor, - maxLine: 1, + color: AppColors.textColor, + maxLine: 2, ), ], )); diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index b3abe6a..7bee72d 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -81,4 +81,9 @@ class AppColors { static const Color infoBannerBorderColor = Color(0xFFFFE5B4); static const Color infoBannerIconColor = Color(0xFFCC9B14); static const Color infoBannerTextColor = Color(0xFF856404); + + // Services Page Colors + static const Color eReferralCardColor = Color(0xFFFF8012); + static const Color bloodDonationCardColor = Color(0xFFFF5662); + static const Color bookAppointment = Color(0xFF415364); } From 818534e9f618b460c6a8ee2108aa9e177082a1b6 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Dec 2025 14:18:28 +0300 Subject: [PATCH 5/5] Master merged --- lib/presentation/lab/alphabeticScroll.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart index 7593ea1..e610d71 100644 --- a/lib/presentation/lab/alphabeticScroll.dart +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -192,7 +192,6 @@ class _AlphabetScrollPageState extends State { itemCount: data.length, itemBuilder: (_, index) { final letter = alphabet[index].toLowerCase(); - print("the letter is $letter"); final items = data[letter]!; return Container( key: _offsetKeys[letter],