From 1e5744cf443c03a489c2abc02ecedba67c06456c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 27 Nov 2025 09:49:47 +0300 Subject: [PATCH] Updates & enhancements --- lib/core/api_consts.dart | 4 +- .../immediate_livecare_view_model.dart | 29 ++ lib/features/lab/lab_view_model.dart | 6 +- .../appointment_details_page.dart | 7 +- .../widgets/appointment_card.dart | 4 +- .../widgets/appointment_doctor_card.dart | 22 +- ...mediate_livecare_pending_request_page.dart | 277 +++++++++--------- lib/presentation/home/landing_page.dart | 181 +++++++----- .../insurance_approval_details_page.dart | 6 +- .../insurance/widgets/insurance_history.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 2 +- .../medical_file/medical_file_page.dart | 2 +- .../medical_file/widgets/lab_rad_card.dart | 38 +-- lib/widgets/countdown_timer.dart | 111 +++++++ 14 files changed, 433 insertions(+), 258 deletions(-) create mode 100644 lib/widgets/countdown_timer.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a1f4062..1146736 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -711,7 +711,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -852,7 +852,7 @@ class ApiConsts { static final String addHHCOrder = 'api/HHC/add'; // ************ 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/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 599bc39..7e2d7c9 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; @@ -36,6 +38,10 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { late AppState _appState; + Duration countdownDuration = Duration(minutes: 1, seconds: 0); + late ValueNotifier durationNotifier; + late Timer timer; + initImmediateLiveCare() { _appState = getIt(); immediateLiveCareClinicsList = []; @@ -144,12 +150,19 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { patientLiveCareHistoryList = apiResponse.data!; if (patientLiveCareHistoryList.isNotEmpty) { if (patientLiveCareHistoryList[0].callStatus! < 4) { + countdownDuration = Duration(seconds: 0); + durationNotifier.dispose(); patientHasPendingLiveCareRequest = true; + countdownDuration = Duration(minutes: patientLiveCareHistoryList.first.watingtimeInteger!, seconds: 0); + durationNotifier = ValueNotifier(countdownDuration); + startTimer(); } else { patientHasPendingLiveCareRequest = false; + timer.cancel(); } } else { patientHasPendingLiveCareRequest = false; + timer.cancel(); } notifyListeners(); if (onSuccess != null) { @@ -159,4 +172,20 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { }, ); } + + void startTimer() { + timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime()); + notifyListeners(); + } + + void addTime() { + final seconds = durationNotifier.value.inSeconds - 1; + if (seconds < 0) { + timer?.cancel(); + // Handle end of timer here + // showEndMessage(); + } else { + durationNotifier.value = Duration(seconds: seconds); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index bad4f89..079471c 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -77,14 +77,14 @@ class LabViewModel extends ChangeNotifier { required this.navigationService}); initLabProvider() { - // if (isLabNeedToLoad) { + if (isLabNeedToLoad) { patientLabOrders.clear(); filteredLabOrders.clear(); labOrderTests.clear(); isLabOrdersLoading = true; isLabResultsLoading = true; getPatientLabOrders(); - // } + } notifyListeners(); } @@ -94,7 +94,7 @@ class LabViewModel extends ChangeNotifier { } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - // if (!isLabNeedToLoad) return; + if (!isLabNeedToLoad) return; isLabOrdersLoading = true; patientLabOrders.clear(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 0a1eaf2..20f5538 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -63,10 +63,9 @@ class _AppointmentDetailsPageState extends State { @override void initState() { scheduleMicrotask(() { - // if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { - // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // } + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + + } }); super.initState(); } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e301fbf..5d2ae5c 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -144,9 +144,7 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (isLoading - ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' - : patientAppointmentHistoryResponseModel.doctorNameObj!) + (isLoading ? 'John Doe' : "${patientAppointmentHistoryResponseModel.doctorTitle} ${patientAppointmentHistoryResponseModel.doctorNameObj!}") .toText16(isBold: true, maxlines: 1) .toShimmer2(isShow: isLoading), SizedBox(height: 8.h), diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 8d0f4e7..b2d74ca 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -42,12 +42,18 @@ class AppointmentDoctorCard extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - patientAppointmentHistoryResponseModel.doctorImageURL!, - width: 63.w, - height: 63.h, - fit: BoxFit.cover, - ).circle(100.r), + Column( + children: [ + Image.network( + patientAppointmentHistoryResponseModel.doctorImageURL!, + width: 63.w, + height: 63.h, + fit: BoxFit.cover, + ).circle(100.r), + SizedBox(height: 12.h), + AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), + ], + ), SizedBox(width: 16.w), Expanded( child: Column( @@ -87,10 +93,6 @@ class AppointmentDoctorCard extends StatelessWidget { !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, ), - AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), ], diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart index e9e5c5c..8c8b79c 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart @@ -16,6 +16,7 @@ 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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/countdown_timer.dart'; import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -32,24 +33,24 @@ class _ImmediateLiveCarePendingRequestPageState extends State durationNotifier = ValueNotifier(countdownDuration); - Timer? timer; + // static Duration countdownDuration = Duration(minutes: 1, seconds: 0); + // late ValueNotifier durationNotifier = ValueNotifier(immediateLiveCareViewModel.countdownDuration); + // Timer? timer; @override void initState() { super.initState(); scheduleMicrotask(() { - countdownDuration = Duration(minutes: immediateLiveCareViewModel.patientLiveCareHistoryList[0].watingtimeInteger!, seconds: 0); - durationNotifier = ValueNotifier(countdownDuration); - startTimer(); + // countdownDuration = Duration(minutes: immediateLiveCareViewModel.patientLiveCareHistoryList[0].watingtimeInteger!, seconds: 0); + // durationNotifier = ValueNotifier(immediateLiveCareViewModel.countdownDuration); + // startTimer(); }); } @override void dispose() { - timer?.cancel(); - durationNotifier.dispose(); + // timer?.cancel(); + // durationNotifier.dispose(); super.dispose(); } @@ -85,7 +86,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State( - valueListenable: durationNotifier, + valueListenable: immediateLiveCareVM.durationNotifier, builder: (context, duration, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -173,133 +174,133 @@ class _ImmediateLiveCarePendingRequestPageState extends State addTime()); - setState(() {}); - } - - void addTime() { - final seconds = durationNotifier.value.inSeconds - 1; - if (seconds < 0) { - timer?.cancel(); - // Handle end of timer here - // showEndMessage(); - } else { - durationNotifier.value = Duration(seconds: seconds); - } - } - - Future _onWillPop() async { - timer?.cancel(); - Navigator.of(context).pop(); - return true; - } - - Widget buildTime(Duration duration) { - String twoDigits(int n) => n.toString().padLeft(2, '0'); - final hours = twoDigits(duration.inHours); - final minutes = twoDigits(duration.inMinutes.remainder(60)); - final seconds = twoDigits(duration.inSeconds.remainder(60)); - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTimeColumn(hours, "Hours".needTranslation), - buildTimeColumn(minutes, "Mins".needTranslation), - buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), - ], - ); - } - - Widget buildTimeColumn(String time, String label, {bool isLast = false}) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - children: [ - buildDigit(time[0]), - buildDigit(time[1]), - if (!isLast) buildTimeSeparator(), - ], - ), - buildLabel(label), - ], - ); - } - - Widget buildDigit(String digit) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - // margin: const EdgeInsets.symmetric(horizontal: 2), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: ClipRect( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 600), - switchInCurve: Curves.easeOutExpo, - switchOutCurve: Curves.easeInExpo, - transitionBuilder: (Widget child, Animation animation) { - return Stack( - children: [ - SlideTransition( - position: Tween( - begin: const Offset(0, -1), - end: const Offset(0, 1), - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeOutCubic, - )), - child: FadeTransition( - opacity: animation, - child: child, - ), - ), - SlideTransition( - position: Tween( - begin: const Offset(0, -1), - end: const Offset(0, 0), - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.bounceIn, - )), - child: FadeTransition( - opacity: animation, - child: child, - ), - ), - ], - ); - }, - child: Text( - digit, - key: ValueKey(digit), - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black, - fontSize: 20.f, - ), - ), - ), - ), - ); - } - - Widget buildLabel(String label) { - return label.toText14(isBold: true); - } - - Widget buildTimeSeparator() { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 2.0), - child: Text( - ":", - style: TextStyle( - color: Colors.black, - fontSize: 20, - ), - ), - ); - } + // void startTimer() { + // // timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime()); + // // setState(() {}); + // } + // + // void addTime() { + // final seconds = durationNotifier.value.inSeconds - 1; + // if (seconds < 0) { + // timer?.cancel(); + // // Handle end of timer here + // // showEndMessage(); + // } else { + // durationNotifier.value = Duration(seconds: seconds); + // } + // } + // + // Future _onWillPop() async { + // timer?.cancel(); + // Navigator.of(context).pop(); + // return true; + // } + // + // Widget buildTime(Duration duration) { + // String twoDigits(int n) => n.toString().padLeft(2, '0'); + // final hours = twoDigits(duration.inHours); + // final minutes = twoDigits(duration.inMinutes.remainder(60)); + // final seconds = twoDigits(duration.inSeconds.remainder(60)); + // + // return Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // buildTimeColumn(hours, "Hours".needTranslation), + // buildTimeColumn(minutes, "Mins".needTranslation), + // buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), + // ], + // ); + // } + // + // Widget buildTimeColumn(String time, String label, {bool isLast = false}) { + // return Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Row( + // children: [ + // buildDigit(time[0]), + // buildDigit(time[1]), + // if (!isLast) buildTimeSeparator(), + // ], + // ), + // buildLabel(label), + // ], + // ); + // } + // + // Widget buildDigit(String digit) { + // return Container( + // padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + // // margin: const EdgeInsets.symmetric(horizontal: 2), + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(8), + // ), + // child: ClipRect( + // child: AnimatedSwitcher( + // duration: const Duration(milliseconds: 600), + // switchInCurve: Curves.easeOutExpo, + // switchOutCurve: Curves.easeInExpo, + // transitionBuilder: (Widget child, Animation animation) { + // return Stack( + // children: [ + // SlideTransition( + // position: Tween( + // begin: const Offset(0, -1), + // end: const Offset(0, 1), + // ).animate(CurvedAnimation( + // parent: animation, + // curve: Curves.easeOutCubic, + // )), + // child: FadeTransition( + // opacity: animation, + // child: child, + // ), + // ), + // SlideTransition( + // position: Tween( + // begin: const Offset(0, -1), + // end: const Offset(0, 0), + // ).animate(CurvedAnimation( + // parent: animation, + // curve: Curves.bounceIn, + // )), + // child: FadeTransition( + // opacity: animation, + // child: child, + // ), + // ), + // ], + // ); + // }, + // child: Text( + // digit, + // key: ValueKey(digit), + // style: TextStyle( + // fontWeight: FontWeight.bold, + // color: Colors.black, + // fontSize: 20.f, + // ), + // ), + // ), + // ), + // ); + // } + // + // Widget buildLabel(String label) { + // return label.toText14(isBold: true); + // } + // + // Widget buildTimeSeparator() { + // return const Padding( + // padding: EdgeInsets.symmetric(horizontal: 2.0), + // child: Text( + // ":", + // style: TextStyle( + // color: Colors.black, + // fontSize: 20, + // ), + // ), + // ); + // } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index ce2e514..fe0268d 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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/int_extensions.dart'; @@ -44,6 +45,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/countdown_timer.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:provider/provider.dart'; @@ -253,20 +255,107 @@ class _LandingPageState extends State { builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); + return (immediateLiveCareViewModel.patientHasPendingLiveCareRequest && index == 0) + ? Column( + children: [ + SizedBox(height: 12.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate LiveCare Request".needTranslation.toText16(isBold: true), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppCustomChipWidget( + labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), + ], + ), + SizedBox(height: 10.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".toText14(isBold: true), + SizedBox(height: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Expected waiting time: ".toText12(isBold: true), + SizedBox(height: 8.h), + ValueListenableBuilder( + valueListenable: immediateLiveCareViewModel.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), + SizedBox(height: 12.h), + ], + ), + // CustomButton( + // text: "View Details".needTranslation, + // onPressed: () async { + // Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + // }, + // backgroundColor: Color(0xffFEE9EA), + // borderColor: Color(0xffFEE9EA), + // textColor: Color(0xffED1C2B), + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + // height: 40.h, + // ), + ], + ), + ), + ).paddingSymmetrical(0.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + }), + SizedBox(height: 12.h), + ], + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: + myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); }, ) : Container( @@ -304,64 +393,6 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h); }, ), - // Consumer for LiveCare pending request - Consumer( - builder: (context, immediateLiveCareVM, child) { - return immediateLiveCareVM.patientHasPendingLiveCareRequest - ? Column( - children: [ - SizedBox(height: 12.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), - ), - width: double.infinity, - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, - backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), - textColor: AppColors.alertColor, - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain), - ], - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "You have a pending LiveCare request".needTranslation.toText12(isBold: true), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 20.h, - height: 15.h, - fit: BoxFit.contain, - ), - ], - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - }), - SizedBox(height: 12.h), - ], - ) - : SizedBox(height: 12.h); - }, - ), // Consumer for ER Online Check-In pending request Consumer( @@ -421,6 +452,7 @@ class _LandingPageState extends State { : SizedBox(height: 12.h); }, ), + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -576,10 +608,13 @@ class _LandingPageState extends State { isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - authVM.loginWithFingerPrintFace(() { + authVM.loginWithFingerPrintFace(() async { isDone = true; cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true); setState(() {}); + await Future.delayed(Duration(milliseconds: 3000)).then((value) { + Navigator.pop(context); + }); }); }, ); diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index 437c870..150b15c 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -118,7 +118,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { ), child: Padding( padding: EdgeInsets.all(16.h), - child: Column( + child: insuranceApprovalResponseModel.apporvalDetails != null ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "Approval Details".toText16(isBold: true), @@ -128,7 +128,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { children: [ LocaleKeys.procedureName.tr(context: context).toText14(isBold: true), Expanded( - child: insuranceApprovalResponseModel.apporvalDetails!.procedureName! + child: (insuranceApprovalResponseModel.apporvalDetails!.procedureName ?? "") .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, textAlignment: TextAlign.end, maxLine: 2), ), ], @@ -150,7 +150,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { ], ), ], - ), + ) : SizedBox.shrink(), ), ) ], diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index a5114dd..3e6ee86 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -68,7 +68,7 @@ class InsuranceHistory extends StatelessWidget { Row( children: [ CustomButton( - text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription!, + text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription ?? "", onPressed: () {}, backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), borderColor: AppColors.primaryRedColor.withOpacity(0.0), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 4ffd979..184f713 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/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 diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 2cf584e..1652812 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -85,7 +85,6 @@ class _MedicalFilePageState extends State { appState = getIt.get(); scheduleMicrotask(() { if (appState.isAuthenticated) { - labViewModel.initLabProvider(); insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); @@ -458,6 +457,7 @@ class _MedicalFilePageState extends State { labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, isLoading: labViewModel.isLabOrdersLoading, ).onPress(() { + labViewModel.initLabProvider(); Navigator.of(context).push( CustomPageRoute( page: LabOrdersPage(), diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index 1a56baf..696b332 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -43,29 +43,29 @@ class LabRadCard extends StatelessWidget { Flexible(child: labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.r, height: 32.h)), ], ), - SizedBox(height: 16.h), - labOrderTests.isNotEmpty - ? ListView.separated( - scrollDirection: Axis.vertical, - padding: EdgeInsets.zero, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (cxt, index) { - return labOrderTests[index] - .toText12(isBold: true, maxLine: 1) - .toShimmer2(isShow: false, radius: 6.r, height: 24.h, width: 120.w) - .toShimmer2(isShow: isLoading); - }, - separatorBuilder: (cxt, index) => SizedBox(height: 8.h), - itemCount: 3, - ) - : "You don't have any records yet".needTranslation.toText13( - color: AppColors.greyTextColor, isCenter: true), + // SizedBox(height: 16.h), + // labOrderTests.isNotEmpty + // ? ListView.separated( + // scrollDirection: Axis.vertical, + // padding: EdgeInsets.zero, + // physics: NeverScrollableScrollPhysics(), + // shrinkWrap: true, + // itemBuilder: (cxt, index) { + // return labOrderTests[index] + // .toText12(isBold: true, maxLine: 1) + // .toShimmer2(isShow: false, radius: 6.r, height: 24.h, width: 120.w) + // .toShimmer2(isShow: isLoading); + // }, + // separatorBuilder: (cxt, index) => SizedBox(height: 8.h), + // itemCount: 3, + // ) + // : "You don't have any records yet".needTranslation.toText13( + // color: AppColors.greyTextColor, isCenter: true), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox.shrink(), + "View All".toText12(isBold: true), Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( diff --git a/lib/widgets/countdown_timer.dart b/lib/widgets/countdown_timer.dart new file mode 100644 index 0000000..165a833 --- /dev/null +++ b/lib/widgets/countdown_timer.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; + +Widget buildTime(Duration duration, {bool isHomePage = false}) { + String twoDigits(int n) => n.toString().padLeft(2, '0'); + final hours = twoDigits(duration.inHours); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTimeColumn(hours, "Hours".needTranslation), + buildTimeColumn(minutes, "Mins".needTranslation), + buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), + ], + ); +} + +Widget buildTimeColumn(String time, String label, {bool isLast = false}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + buildDigit(time[0]), + buildDigit(time[1]), + if (!isLast) buildTimeSeparator(), + ], + ), + buildLabel(label), + ], + ); +} + +Widget buildDigit(String digit) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + // margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: ClipRect( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 600), + switchInCurve: Curves.easeOutExpo, + switchOutCurve: Curves.easeInExpo, + transitionBuilder: (Widget child, Animation animation) { + return Stack( + children: [ + SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: const Offset(0, 1), + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + )), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: const Offset(0, 0), + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.bounceIn, + )), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + ], + ); + }, + child: Text( + digit, + key: ValueKey(digit), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 14.f, + ), + ), + ), + ), + ); +} + +Widget buildLabel(String label) { + return label.toText12(isBold: true); +} + +Widget buildTimeSeparator() { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 2.0), + child: Text( + ":", + style: TextStyle( + color: Colors.black, + fontSize: 20, + ), + ), + ); +}