From 77242825bbfd2653b310de8836409303d1fd91c3 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 18 Nov 2025 10:54:57 +0300 Subject: [PATCH] fix toggle --- .../active_prescriptions_view_model.dart | 168 +--- .../active_prescriptions_response_model.dart | 2 +- .../active_medication_page.dart | 730 ++++++++++++------ 3 files changed, 512 insertions(+), 388 deletions(-) diff --git a/lib/features/active_prescriptions/active_prescriptions_view_model.dart b/lib/features/active_prescriptions/active_prescriptions_view_model.dart index 03f84ea..e4da04d 100644 --- a/lib/features/active_prescriptions/active_prescriptions_view_model.dart +++ b/lib/features/active_prescriptions/active_prescriptions_view_model.dart @@ -5,36 +5,24 @@ import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescri import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class ActivePrescriptionsViewModel extends ChangeNotifier { - bool isActivePrescriptionsDetailsLoading = false; - late ActivePrescriptionsRepo activePrescriptionsRepo; late ErrorHandlerService errorHandlerService; + List activePrescriptionsDetailsList = []; ActivePrescriptionsViewModel({ required this.activePrescriptionsRepo, required this.errorHandlerService, }); - List activePrescriptionsDetailsList = []; - - initActivePrescriptionsViewModel() { - getActiveMedications(); - notifyListeners(); - } - - setPrescriptionsDetailsLoading() { - isActivePrescriptionsDetailsLoading = true; - notifyListeners(); - } - - // Get medications list Future getActiveMedications({ Function(dynamic)? onSuccess, Function(String)? onError, }) async { - final result = await activePrescriptionsRepo.getActivePrescriptionsDetails(); + final result = + await activePrescriptionsRepo.getActivePrescriptionsDetails(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async => + await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 1) { activePrescriptionsDetailsList = apiResponse.data ?? []; @@ -56,140 +44,58 @@ class ActivePrescriptionsViewModel extends ChangeNotifier { return DateTime.tryParse(date) ?? DateTime.now(); } - // Extract numeric value ( "3 / week" → 3) - int extractNumberFromFrequency(String? frequency) { - if (frequency == null) return 1; - final m = RegExp(r'(\d+)').firstMatch(frequency); - if (m != null) return int.tryParse(m.group(1)!) ?? 1; - return 1; - } - - // Generate medication days based on frequency text List generateMedicationDays(ActivePrescriptionsResponseModel med) { final start = parseDate(med.startDate); final duration = med.days ?? 0; - final frequency = (med.frequency ?? "").toLowerCase().trim(); - - List result = []; - if (duration <= 0) return result; - - // Every N hours ( "Every Six Hours", "Every 8 hours") - if (frequency.contains("hour")) { - final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); - int intervalHours = 0; - - if (match != null) { - intervalHours = int.tryParse(match.group(1)!) ?? 0; - } else { - // handle text numbers like "Every six hours" - final textNum = { - "one": 1, - "two": 2, - "three": 3, - "four": 4, - "five": 5, - "six": 6, - "seven": 7, - "eight": 8, - "nine": 9, - "ten": 10, - "twelve": 12, - }; - for (var key in textNum.keys) { - if (frequency.contains(key)) { - intervalHours = textNum[key]!; - break; - } - } - } - - if (intervalHours > 0) { - for (int day = 0; day < duration; day++) { - final dayStart = start.add(Duration(days: day)); - for (int hour = 0; hour < 24; hour += intervalHours) { - result.add(DateTime(dayStart.year, dayStart.month, dayStart.day, hour)); - } - } - return result; - } - } - - // Daily (every day) - if (frequency.contains("day") && - !frequency.contains("every other") && - !frequency.contains("every ")) { - for (int i = 0; i < duration; i++) { - result.add(start.add(Duration(days: i))); - } - } - - // Every other day - else if (frequency.contains("every other day")) { - for (int i = 0; i < duration; i += 2) { - result.add(start.add(Duration(days: i))); - } + if (duration <= 0) return []; + final f = (med.frequency ?? "").toLowerCase().trim(); + int intervalDays = 1; + + if (f.contains("every six hours") || + f.contains("every 6 hours") || + f.contains("every four hours") || + f.contains("every 4 hours") || + f.contains("every eight hours") || + f.contains("every 8 hours") || + f.contains("every 12 hours") || + f.contains("every twelve hours") || + f.contains("every 24 hours") || + f.contains("3 times a day") || + f.contains("once a day")) { + intervalDays = 1; } - - // Every N days → e.g. "Every 3 days", "Every 5 days" - else if (frequency.contains("every") && frequency.contains("day")) { - final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); - final interval = match != null ? int.tryParse(match.group(1)!) ?? 1 : 1; - for (int i = 0; i < duration; i += interval) { - result.add(start.add(Duration(days: i))); - } + else if (f.contains("once a week")) { + intervalDays = 7; } - - // Once or twice a week - else if (frequency.contains("once a week")) { - for (int i = 0; i < duration; i += 7) { - result.add(start.add(Duration(days: i))); - } - } else if (frequency.contains("twice a week")) { - for (int i = 0; i < duration; i += 3) { - result.add(start.add(Duration(days: i))); - } + else if (f.contains("every 3 days")) { + intervalDays = 3; } - - // Numeric frequency like "3 / week", "2 / week" - else if (frequency.contains("week")) { - int timesPerWeek = extractNumberFromFrequency(frequency); - double interval = 7 / timesPerWeek; - double dayPointer = 0; - - for (int i = 0; i < duration; i++) { - if (i >= dayPointer.floor()) { - result.add(start.add(Duration(days: i))); - dayPointer += interval; - } - } + else if (f.contains("every other day")) { + intervalDays = 2; } - else { - result.add(start); - } - - - final unique = {}; - for (final d in result) { - unique["${d.year}-${d.month}-${d.day}"] = d; + List result = []; + for (int offset = 0; offset < duration; offset += intervalDays) { + result.add(start.add(Duration(days: offset))); } - return unique.values.toList()..sort((a, b) => a.compareTo(b)); + return result; } - bool sameYMD(DateTime a, DateTime b) => a.year == b.year && a.month == b.month && a.day == b.day; - // Filter medications for selected day - List getMedsForSelectedDay(DateTime selectedDate) { - final target = DateTime(selectedDate.year, selectedDate.month, selectedDate.day); + List getMedsForSelectedDay( + DateTime selectedDate) { + final clean = DateTime(selectedDate.year, selectedDate.month, selectedDate.day); + return activePrescriptionsDetailsList.where((med) { final days = generateMedicationDays(med); - return days.any((d) => sameYMD(d, target)); + return days.any((d) => sameYMD(d, clean)); }).toList(); } } + diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart index eb216a6..42faafa 100644 --- a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -35,7 +35,7 @@ class ActivePrescriptionsResponseModel { int? scaleOffset; String? startDate; - // ✅ Added for reminder feature + // Added for reminder feature List selectedDoseTimes = []; bool isReminderOn = false; // toggle status diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index aa2abe5..d27b35d 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -18,6 +18,7 @@ import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; // for date formatting import 'package:provider/provider.dart'; import '../../widgets/loader/bottomsheet_loader.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class ActiveMedicationPage extends StatefulWidget { @@ -32,43 +33,98 @@ class _ActiveMedicationPageState extends State { late DateTime selectedDate; List selectedDayMeds = []; ActivePrescriptionsViewModel? activePreVM; - Map medReminderStatus = {}; + + + Map medReminderStatus = {}; + + String _buildMedKey(ActivePrescriptionsResponseModel med) { + return "${med.itemId}_${med.startDate}_${med.days}_${med.frequency}"; + } + + int _getDosesCount(ActivePrescriptionsResponseModel med) { + return med.frequencyNumber ?? 1; + } @override - void initState() { + void initState() { super.initState(); currentDate = DateTime.now(); selectedDate = currentDate; + WidgetsBinding.instance.addPostFrameCallback((_) async { - activePreVM = Provider.of(context, listen: false); + activePreVM = + Provider.of(context, listen: false); LoaderBottomSheet.showLoader(); await activePreVM!.getActiveMedications( - onSuccess: (_) { + onSuccess: (_) async { LoaderBottomSheet.hideLoader(); - final todayMeds = activePreVM!.getMedsForSelectedDay(selectedDate); - setState(() { - selectedDayMeds = todayMeds; + + final todayMeds = + activePreVM!.getMedsForSelectedDay(selectedDate); + setState(() => selectedDayMeds = todayMeds); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await loadSavedReminders(); }); }, onError: (_) { LoaderBottomSheet.hideLoader(); }, ); - activePreVM!.addListener(() { + + activePreVM!.addListener(() { if (!mounted) return; - final medsForDay = activePreVM!.getMedsForSelectedDay(selectedDate); + final medsForDay = + activePreVM!.getMedsForSelectedDay(selectedDate); setState(() => selectedDayMeds = medsForDay); }); }); } + Future loadSavedReminders() async { + final prefs = await SharedPreferences.getInstance(); + + for (final med in activePreVM!.activePrescriptionsDetailsList) { + final medKey = _buildMedKey(med); + final doses = _getDosesCount(med); + + med.selectedDoseTimes = + List.filled(doses, null, growable: false); + + for (int i = 0; i < doses; i++) { + final saved = prefs.getString("doseTime_${medKey}_$i"); + if (saved != null) { + med.selectedDoseTimes[i] = saved; + } + } + + final reminderOn = + prefs.getBool("reminderStatus_$medKey") ?? false; + med.isReminderOn = reminderOn; + medReminderStatus[medKey] = reminderOn; + } + + setState(() {}); + } + + Future saveReminderStatus(String medKey, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool("reminderStatus_$medKey", value); + } + + Future saveDoseTime( + String medKey, int doseIndex, String time) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString("doseTime_${medKey}_$doseIndex", time); + } + + List getUpcomingDays() => + List.generate(7, (index) => currentDate.add(Duration(days: index))); - List getUpcomingDays() => List.generate(7, (index) => currentDate.add(Duration(days: index))); @override Widget build(BuildContext context) { final days = getUpcomingDays(); - final dateText = "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)}"; - return Scaffold( + return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( onBackPressed: () => Navigator.of(context).pop(), @@ -99,13 +155,13 @@ class _ActiveMedicationPageState extends State { }, ), ), - SizedBox(height: 20.h), + SizedBox(height: 20.h), RichText( text: TextSpan( children: [ TextSpan( text: "${selectedDate.day}", - style: TextStyle( + style: TextStyle( color: AppColors.textColor, fontSize: 16, fontWeight: FontWeight.w500, @@ -115,7 +171,7 @@ class _ActiveMedicationPageState extends State { child: Transform.translate( offset: const Offset(0, -4), child: Text( - getSuffix(selectedDate.day), + _getSuffix(selectedDate.day), style: const TextStyle( fontSize: 12, color: AppColors.textColor, @@ -135,121 +191,163 @@ class _ActiveMedicationPageState extends State { ], ), ), - Text("Medications".needTranslation, + Text("Medications".needTranslation, style: TextStyle( color: AppColors.primaryRedBorderColor, fontSize: 12.f, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), + SizedBox(height: 16.h), Expanded( child: SingleChildScrollView( - child: selectedDayMeds.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: selectedDayMeds.length, - itemBuilder: (context, index) { - final med = selectedDayMeds[index]; - final doses = med.doseDailyQuantity ?? 1; - med.selectedDoseTimes ??= List.filled(doses, null); - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - margin: EdgeInsets.all(10), - child: Column( - children: [ - _buildMedHeader(med), - Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: selectedDayMeds.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: selectedDayMeds.length, + itemBuilder: (context, index) { + final med = selectedDayMeds[index]; + final doses = _getDosesCount(med); + if (med.selectedDoseTimes.length != doses) { + final old = med.selectedDoseTimes; + med.selectedDoseTimes = + List.filled(doses, null, + growable: false); + for (int i = 0; + i < old.length && i < doses; + i++) { + med.selectedDoseTimes[i] = old[i]; + } + } + + return Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + margin: EdgeInsets.all(10), + child: Column( + children: [ + _buildMedHeader(med), + Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + // Utils.buildSvgWithAssets( + // icon: AppAssets., + // height: 18.h, + // width: 18.h, + // iconColor: + // AppColors.lightGreyTextColor, + // ), + Icon( + Icons.info_outline, + color: AppColors + .lightGreyTextColor, + size: 18, + ), + SizedBox(width: 6.h), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Remarks: " + .needTranslation, + style: TextStyle( + color: + AppColors.textColor, + fontWeight: + FontWeight.w600, + fontSize: 10, + ), + ), + TextSpan( + text: + "some remarks about the prescription will be here" + .needTranslation, + style: TextStyle( + color: AppColors + .lightGreyTextColor, + fontWeight: + FontWeight.normal, + fontSize: 10, + ), + ), + ], + ), + ), + ), + ], + ).paddingOnly(left: 16, right: 16), + const Divider( + color: AppColors.greyColor), + GestureDetector( + onTap: () => showDoseDialog(med), + child: Row( children: [ - Icon( - Icons.info_outline, - color: AppColors.lightGreyTextColor, - size: 20, + Container( + width: 40.h, + height: 40.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: + BorderRadius.circular(10), + ), + child: + Utils.buildSvgWithAssets( + icon: AppAssets.bell, + height: 24.h, + width: 24.h, + iconColor: + AppColors.greyTextColor, + ), ), - SizedBox(width: 6.h), + SizedBox(width: 12.h), Expanded( - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: "Remarks: ".needTranslation, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Set Reminder" + .needTranslation, style: TextStyle( - color: AppColors.textColor, - fontWeight: FontWeight.w600, - fontSize: 10, - ), - ), - TextSpan( - text: "some remarks about the prescription will be here".needTranslation, + fontSize: 14.f, + fontWeight: + FontWeight.w600, + color: AppColors + .textColor)), + Text( + "Notify me before the consumption time" + .needTranslation, style: TextStyle( - color: AppColors.lightGreyTextColor, - fontWeight: FontWeight.normal, - fontSize: 10, - ), - ), - ], - ), + fontSize: 12.f, + color: AppColors + .textColorLight, + )), + ], ), ), + _buildToggle(med), ], - ).paddingOnly(left: 16, right: 16), - const Divider(color: AppColors.greyColor), - // Reminder Section - GestureDetector( - onTap: () => showDoseDialog(med, index), - child: Row( - children: [ - Container( - width: 40.h, - height: 40.h, - alignment: Alignment.center, - decoration: BoxDecoration( - color: AppColors.greyColor, - borderRadius: BorderRadius.circular(10), - ), - child: Utils.buildSvgWithAssets( - icon: AppAssets.bell, - height: 24.h, - width: 24.h, - iconColor: AppColors.greyTextColor, - ), - ), - SizedBox(width: 12.h), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text("Set Reminder".needTranslation, - style: TextStyle( - fontSize: 14.f, - fontWeight: FontWeight.w600, - color: AppColors.textColor)), - Text("Notify me before the consumption time".needTranslation, - style: TextStyle( - fontSize: 12.f, - color: AppColors.textColorLight, - )), - ], - ), - ), - _buildToggle(index) - ], - ).paddingAll(16), - ), - const Divider(color: AppColors.greyColor), - _buildButtons(), - ], - ), - ); - }, - ) - : Utils.getNoDataWidget(context, - noDataText: "No medications today".needTranslation), + ).paddingAll(16), + ), + const Divider( + color: AppColors.greyColor), + _buildButtons(), + ], + ), + ); + }, + ) + : Utils.getNoDataWidget( + context, + noDataText: + "No medications today".needTranslation, + ), ), ), ], @@ -257,55 +355,67 @@ class _ActiveMedicationPageState extends State { ); } - //medicine card - Widget _buildMedHeader(ActivePrescriptionsResponseModel med) => Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Container( - width: 59.h, - height: 59.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.spacerLineColor, - borderRadius: 30.r, - hasShadow: false, + // medicine card + Widget _buildMedHeader(ActivePrescriptionsResponseModel med) => + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Container( + width: 59.h, + height: 59.h, + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.spacerLineColor, + borderRadius: 30.r, + hasShadow: false, + ), + child: Utils.buildImgWithNetwork( + url: med.productImageString ?? "", + iconColor: Colors.transparent) + .circle(52.h)), + ), + SizedBox(width: 12.h), + Expanded( + child: Text( + med.itemDescription ?? "", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor), ), - child: Utils.buildImgWithNetwork( - url: med.productImageString ?? "" ).circle(52.h) - ), - ), - SizedBox(width: 12.h), - Expanded( - child: Text( - med.itemDescription ?? "", - style: TextStyle( - fontSize: 16.f, - fontWeight: FontWeight.w600, - color: AppColors.textColor), + ), + ]), + SizedBox(height: 12.h), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + AppCustomChipWidget( + labelText: + "Route: ${med.route}".needTranslation), + AppCustomChipWidget( + labelText: + "Frequency: ${med.frequency}".needTranslation), + AppCustomChipWidget( + labelText: + "Daily Dose: ${med.doseDailyQuantity}" + .needTranslation), + AppCustomChipWidget( + labelText: + "Duration: ${med.days}".needTranslation), + ], ), - ), - ]), - SizedBox(height: 12.h), - Wrap( - spacing: 4, - runSpacing: 4, - children: [ - AppCustomChipWidget(labelText: "Route: ${med.route}".needTranslation), - AppCustomChipWidget(labelText: "Frequency: ${med.frequency}".needTranslation), - AppCustomChipWidget(labelText: "Daily Dose: ${med.doseDailyQuantity}".needTranslation), - AppCustomChipWidget(labelText: "Duration: ${med.days}".needTranslation), ], ), - ], - ), - ); + ); Widget _buildButtons() => Padding( - padding: EdgeInsets.all(16), + padding: EdgeInsets.all(16), child: Row(children: [ Expanded( child: CustomButton( @@ -317,24 +427,28 @@ class _ActiveMedicationPageState extends State { textColor: AppColors.errorColor, ), ), - SizedBox(width: 12.h), + SizedBox(width: 12.h), Expanded( child: CustomButton( - text: "Read Instructions".needTranslation, fontSize: 13.f, onPressed: () {})), + text: "Read Instructions".needTranslation, + fontSize: 13.f, + onPressed: () {})), ]), ); - Widget _buildToggle(int index) { - final value = medReminderStatus[index] ?? false; + Widget _buildToggle(ActivePrescriptionsResponseModel med) { + final medKey = _buildMedKey(med); + final value = medReminderStatus[medKey] ?? false; return GestureDetector( onTap: () async { - await showDoseDialog(selectedDayMeds[index], index); - setState(() { - if ((selectedDayMeds[index].selectedDoseTimes ?? []).any((t) => t != null)) { - medReminderStatus[index] = true; - } - }); + await showDoseDialog(med); + final hasTime = + (med.selectedDoseTimes).any((t) => t != null); + medReminderStatus[medKey] = hasTime; + await saveReminderStatus(medKey, hasTime); + + setState(() {}); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), @@ -342,11 +456,14 @@ class _ActiveMedicationPageState extends State { height: 28.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: value ? AppColors.lightGreenColor : AppColors.greyColor.withOpacity(0.3), + color: value + ? AppColors.lightGreenColor + : AppColors.greyColor.withOpacity(0.3), ), child: AnimatedAlign( duration: const Duration(milliseconds: 200), - alignment: value ? Alignment.centerRight : Alignment.centerLeft, + alignment: + value ? Alignment.centerRight : Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(3), child: Container( @@ -354,7 +471,9 @@ class _ActiveMedicationPageState extends State { height: 22.h, decoration: BoxDecoration( shape: BoxShape.circle, - color: value ? AppColors.textGreenColor : AppColors.greyTextColor, + color: value + ? AppColors.textGreenColor + : AppColors.greyTextColor, ), ), ), @@ -363,10 +482,16 @@ class _ActiveMedicationPageState extends State { ); } - Future showDoseDialog(ActivePrescriptionsResponseModel med, int medIndex) { - final doses = med.frequencyNumber ?? 1; + + Future showDoseDialog(ActivePrescriptionsResponseModel med) { + final doses = _getDosesCount(med); if (med.selectedDoseTimes.length != doses) { - med.selectedDoseTimes = List.generate(doses, (_) => null); + final old = med.selectedDoseTimes; + med.selectedDoseTimes = + List.filled(doses, null, growable: false); + for (int i = 0; i < old.length && i < doses; i++) { + med.selectedDoseTimes[i] = old[i]; + } } return showModalBottomSheet( @@ -378,19 +503,22 @@ class _ActiveMedicationPageState extends State { height: 520.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bottomSheetBgColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + customBorder: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), hasShadow: true, ), - child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Text( + Text( "Reminders".needTranslation, style: TextStyle( fontSize: 20.f, @@ -400,11 +528,17 @@ class _ActiveMedicationPageState extends State { ), GestureDetector( onTap: () => Navigator.pop(context), - child: Icon(Icons.close, color:AppColors.blackBgColor), + child: Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + height: 24.h, + width: 24.h, + iconColor: + AppColors.blackBgColor, + ), ), ], ), - SizedBox(height: 20.h), + SizedBox(height: 20.h), Expanded( child: ListView.builder( itemCount: doses, @@ -415,64 +549,91 @@ class _ActiveMedicationPageState extends State { AppColors.labelColorYellow, AppColors.purpleBg ][doseIndex % 4]; - - final doseLabel = "${doseIndex + 1}${getSuffix(doseIndex + 1)}"; - final time = med.selectedDoseTimes[doseIndex] ?? "Not set yet"; - + final doseLabel = + "${doseIndex + 1}${_getSuffix(doseIndex + 1)}"; + final time = + med.selectedDoseTimes[doseIndex] ?? + "Not set yet"; return GestureDetector( onTap: () { Navigator.pop(context); - showTimePickerSheet(med, medIndex, doseIndex); + showTimePickerSheet(med, doseIndex); }, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 16.r, hasShadow: false, ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.symmetric( - vertical: 6, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 14), decoration: BoxDecoration( color: badgeColor, borderRadius: BorderRadius.circular(12), ), - child: Text( - doseLabel, - style: TextStyle( - color: AppColors.whiteColor, - fontWeight: FontWeight.bold, - fontSize: 16.f, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "${doseIndex + 1}", + style: TextStyle( + color: AppColors.whiteColor, + fontWeight: FontWeight.bold, + fontSize: 16.f, + ), + ), + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -4), + child: Text( + _getSuffix(doseIndex + 1), + style: TextStyle( + color: AppColors.whiteColor, + fontSize: 10.f, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], ), ), ), SizedBox(height: 8.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( "Set reminder for $doseLabel dose", - style: TextStyle( + style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.bold, fontSize: 16.f, ), ), ), - Icon(Icons.arrow_forward_outlined, - size: 24.w, color: AppColors.textColor), + Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + height: 24.h, + width: 24.h, + iconColor: + AppColors.textColor, + ), ], ), - SizedBox(height: 4.h), + SizedBox(height: 4.h), Text( time, - style: TextStyle( + style: TextStyle( fontSize: 12.f, color: AppColors.greyTextColor, fontWeight: FontWeight.w500, @@ -494,7 +655,7 @@ class _ActiveMedicationPageState extends State { void showTimePickerSheet( - ActivePrescriptionsResponseModel med, int medIndex, int doseIndex) { + ActivePrescriptionsResponseModel med, int doseIndex) { showModalBottomSheet( context: context, isScrollControlled: true, @@ -502,20 +663,24 @@ class _ActiveMedicationPageState extends State { builder: (_) => Container( width: double.infinity, height: 460.h, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: AppColors.bottomSheetBgColor, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), ), child: ReminderTimerDialog( med: med, - frequencyNumber: med.doseDailyQuantity ?? 1, + frequencyNumber: _getDosesCount(med), doseIndex: doseIndex, - onTimeSelected: (String time) { - setState(() { - med.selectedDoseTimes[doseIndex] = time; - medReminderStatus[medIndex] = true; - }); + onTimeSelected: (String time) async { + final medKey = _buildMedKey(med); + med.selectedDoseTimes[doseIndex] = time; + await saveDoseTime(medKey, doseIndex, time); + medReminderStatus[medKey] = true; + await saveReminderStatus(medKey, true); + setState(() {}); }, ), ), @@ -529,7 +694,8 @@ class _ActiveMedicationPageState extends State { return GestureDetector( onTap: () { final vm = - Provider.of(context, listen: false); + Provider.of(context, + listen: false); setState(() { selectedDate = date; selectedDayMeds = vm.getMedsForSelectedDay(date); @@ -544,10 +710,11 @@ class _ActiveMedicationPageState extends State { ? AppColors.secondaryLightRedBorderColor : Colors.transparent, border: Border.all( - color: isSelected - ? AppColors.primaryRedBorderColor - : AppColors.spacerLineColor, - width: 1), + color: isSelected + ? AppColors.primaryRedBorderColor + : AppColors.spacerLineColor, + width: 1, + ), ), child: Padding( padding: const EdgeInsets.all(8.0), @@ -563,7 +730,7 @@ class _ActiveMedicationPageState extends State { fontSize: 11.f, fontWeight: FontWeight.w500), ), - SizedBox(height: 5.h), + SizedBox(height: 5.h), Text("${date.day}", style: TextStyle( fontSize: 16.f, @@ -577,14 +744,14 @@ class _ActiveMedicationPageState extends State { ); } - String getSuffix(int day) { + String _getSuffix(int day) { if (day == 1 || day == 21 || day == 31) return "st"; if (day == 2 || day == 22) return "nd"; if (day == 3 || day == 23) return "rd"; return "th"; } -} +} class ReminderTimerDialog extends StatefulWidget { @@ -617,32 +784,94 @@ class _ReminderTimerDialogState extends State { ["06:00 PM", "07:00 PM", "08:00 PM", "09:00 PM"], // Evening ]; + String _getSuffix(int number) { + if (number == 1 || number == 21 || number == 31) return "st"; + if (number == 2 || number == 22) return "nd"; + if (number == 3 || number == 23) return "rd"; + return "th"; + } + @override Widget build(BuildContext context) { final int bucket = widget.doseIndex.clamp(0, 2); final List times = presetTimes[bucket]; + return Padding( padding: const EdgeInsets.all(16), child: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bottomSheetBgColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + customBorder: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), hasShadow: true, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Time for ${widget.doseIndex + 1} dose".needTranslation, - style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.bold), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Time for ", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + TextSpan( + text: "${widget.doseIndex + 1}", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -6), + child: Text( + _getSuffix(widget.doseIndex + 1), + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + ), + ), + TextSpan( + text: " reminder", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child:Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + height: 24.h, + width: 24.h, + iconColor: + AppColors.blackBgColor, + ),), + ], ), - SizedBox(height: 12.h), - // Preset times + SizedBox(height: 12.h), Wrap( spacing: 8, runSpacing: 8, - alignment: WrapAlignment.start, children: times.map((t) { bool selected = _selectedTime == t; return AppCustomChipWidget( @@ -660,7 +889,7 @@ class _ReminderTimerDialogState extends State { ), borderRadius: BorderRadius.circular(12), ), - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), onChipTap: () { setState(() { _selectedTime = t; @@ -672,27 +901,25 @@ class _ReminderTimerDialogState extends State { ); }).toList(), ), - SizedBox(height: 25.h), + SizedBox(height: 25.h), GestureDetector( onTap: () { - setState(() { - showPicker = !showPicker; - }); + setState(() => showPicker = !showPicker); }, child: Center( child: Column( children: [ Text( bigTimeText, - style: TextStyle( + style: TextStyle( fontSize: 48.f, fontWeight: FontWeight.bold, - color: AppColors.textColor + color: AppColors.textColor, ), ), Text( selectedTime.period == DayPeriod.am ? "AM" : "PM", - style: TextStyle( + style: TextStyle( fontSize: 20.f, fontWeight: FontWeight.bold, color: AppColors.greyTextColor, @@ -702,8 +929,7 @@ class _ReminderTimerDialogState extends State { ), ), ), - SizedBox(height: 15.h), - // Time picker + SizedBox(height: 15.h), if (showPicker) SizedBox( height: 100.h, @@ -717,20 +943,16 @@ class _ReminderTimerDialogState extends State { selectedTime.hour, selectedTime.minute, ), - onDateTimeChanged: (DateTime newTime) { + onDateTimeChanged: (newTime) { setState(() { _selectedTime = null; - selectedTime = TimeOfDay( - hour: newTime.hour, - minute: newTime.minute, - ); - bigTimeText = - selectedTime.format(context).split(" ")[0]; + selectedTime = TimeOfDay(hour: newTime.hour, minute: newTime.minute); + bigTimeText = selectedTime.format(context).split(" ")[0]; }); }, ), ), - SizedBox(height: 25.h), + SizedBox(height: 25.h), Row( children: [ Expanded( @@ -745,8 +967,7 @@ class _ReminderTimerDialogState extends State { ), ), onPressed: () async { - final selectedFormattedTime = - selectedTime.format(context); + final selectedFormattedTime = selectedTime.format(context); widget.onTimeSelected(selectedFormattedTime); try { final parts = selectedFormattedTime.split(":"); @@ -756,7 +977,6 @@ class _ReminderTimerDialogState extends State { if (isPM && hour != 12) hour += 12; if (!isPM && hour == 12) hour = 0; int totalMinutes = hour * 60 + minute; - // Call setCalender() await setCalender( context, eventId: widget.med.itemId.toString(), @@ -768,21 +988,18 @@ class _ReminderTimerDialogState extends State { route: widget.med.route ?? "", ); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Reminder added to calendar ✅".needTranslation)), + SnackBar(content: Text("Reminder added to calendar ✅".needTranslation)), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text("Error while setting calendar: $e".needTranslation)), + SnackBar(content: Text("Error while setting calendar: $e".needTranslation)), ); } Navigator.pop(context); }, child: Text( LocaleKeys.save.tr(), - style: TextStyle( + style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16.f, ), @@ -797,7 +1014,6 @@ class _ReminderTimerDialogState extends State { ); } - TimeOfDay _parseTime(String t) { try { int hour = int.parse(t.split(":")[0]); @@ -806,7 +1022,7 @@ class _ReminderTimerDialogState extends State { if (pm && hour != 12) hour += 12; if (!pm && hour == 12) hour = 0; return TimeOfDay(hour: hour, minute: minute); - } catch (e) { + } catch (_) { return TimeOfDay.now(); } } @@ -815,3 +1031,5 @@ class _ReminderTimerDialogState extends State { + +