diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e1749d6..d3899285 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -39,7 +39,7 @@ - + @@ -52,7 +52,6 @@ - diff --git a/assets/images/svg/cloudyweather.svg b/assets/images/svg/cloudyweather.svg new file mode 100644 index 00000000..6f8ebb47 --- /dev/null +++ b/assets/images/svg/cloudyweather.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/evening.svg b/assets/images/svg/evening.svg new file mode 100644 index 00000000..7932d78d --- /dev/null +++ b/assets/images/svg/evening.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/morning.svg b/assets/images/svg/morning.svg new file mode 100644 index 00000000..0f0d9904 --- /dev/null +++ b/assets/images/svg/morning.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 3b7253db..f451f0d5 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -64,6 +64,8 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; @@ -164,6 +166,7 @@ class AppDependencies { getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => WeatherRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => HealthTrackersRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt())); @@ -299,6 +302,8 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt())); + //commenting this because its already define there was on run time error because of this. getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 05f82208..fc3990f9 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -41,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier { List patientSickLeavesViewList = []; bool isSickLeavesSortByClinic = true; + // bool isSickLeavesDataNeedsReloading = true; List patientAllergiesList = []; @@ -165,13 +166,13 @@ class MedicalFileViewModel extends ChangeNotifier { setIsPatientSickLeaveListLoading(bool val) { // if (val && isSickLeavesDataNeedsReloading) { - patientSickLeaveList.clear(); - patientSickLeavesByClinic.clear(); - patientSickLeavesByHospital.clear(); - patientSickLeavesViewList.clear(); - patientSickLeavePDFBase64 = ""; - isSickLeavesSortByClinic = true; - isPatientSickLeaveListLoading = val; + patientSickLeaveList.clear(); + patientSickLeavesByClinic.clear(); + patientSickLeavesByHospital.clear(); + patientSickLeavesViewList.clear(); + patientSickLeavePDFBase64 = ""; + isSickLeavesSortByClinic = true; + isPatientSickLeaveListLoading = val; // } notifyListeners(); } @@ -774,4 +775,6 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 73f8d331..842c4a61 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -2,6 +2,7 @@ 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_state.dart'; +import 'package:hmg_patient_app_new/core/utils/calender_utils_new.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'; @@ -1109,4 +1110,108 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + /// Creates a reminder for the appointment + /// Shows a bottom sheet to select reminder time and creates calendar event + Future createReminder({ + required BuildContext context, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + DateTime endDate = DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate); + + BottomSheetUtils().showReminderBottomSheet( + context, + endDate, + patientAppointmentHistoryResponseModel.doctorNameObj ?? "", + "${patientAppointmentHistoryResponseModel.appointmentNo}", + "", + "", + title: "Appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}", + description: "${patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${patientAppointmentHistoryResponseModel.appointmentDate}", + onSuccess: () { + setAppointmentReminder(true, patientAppointmentHistoryResponseModel); + if (onSuccess != null) { + onSuccess(true); + } + }, + isMultiAllowed: true, + onMultiDateSuccess: (int selectedIndex) async { + bool isEventAddedOrRemoved = await calender.createOrUpdateEvent( + title: "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}", + description: "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}", + scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + eventId: "${patientAppointmentHistoryResponseModel.appointmentNo}", + location: '', + reminderMinutes: selectedIndex, + ); + + setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel); + + if (onSuccess != null) { + onSuccess(isEventAddedOrRemoved); + } + }, + ); + } catch (e) { + if (onError != null) { + onError(e.toString()); + } + } + } + + /// Deletes the reminder for the appointment + /// Removes the calendar event associated with the appointment + Future deleteReminder({ + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + + bool isEventRemoved = await calender.checkAndRemove( + id: "${patientAppointmentHistoryResponseModel.appointmentNo}", + ); + + // Update the reminder status (inverted because we're removing) + setAppointmentReminder(!isEventRemoved, patientAppointmentHistoryResponseModel); + + if (onSuccess != null) { + onSuccess(isEventRemoved); + } + } catch (e) { + if (onError != null) { + onError(e.toString()); + } + } + } + + /// Toggle reminder for an appointment + /// Creates or deletes reminder based on the current state + Future toggleReminder({ + required BuildContext context, + required bool shouldCreateReminder, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + if (shouldCreateReminder) { + await createReminder( + context: context, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + onSuccess: onSuccess, + onError: onError, + ); + } else { + await deleteReminder( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + onSuccess: onSuccess, + onError: onError, + ); + } + } } diff --git a/lib/features/weather/models/waether_cities_model.dart b/lib/features/weather/models/waether_cities_model.dart new file mode 100644 index 00000000..6a434bff --- /dev/null +++ b/lib/features/weather/models/waether_cities_model.dart @@ -0,0 +1,257 @@ +import 'dart:convert'; + +class GetCityInfoList { + CategoriesNames? categoriesNames; + String? category; + String? categoryValue; + int? cityId; + String? cityName; + String? cityNameN; + ColorName? colorName; + String? createdOn; + String? id; + int? iniceId; + bool? isOrderEmpty; + bool? isValuesReversed; + bool? language; + double? latitude; + double? longitude; + String? name; + int? orderNum; + double? temperature; + String? value; + + GetCityInfoList({ + this.categoriesNames, + this.category, + this.categoryValue, + this.cityId, + this.cityName, + this.cityNameN, + this.colorName, + this.createdOn, + this.id, + this.iniceId, + this.isOrderEmpty, + this.isValuesReversed, + this.language, + this.latitude, + this.longitude, + this.name, + this.orderNum, + this.temperature, + this.value, + }); + + factory GetCityInfoList.fromRawJson(String str) => GetCityInfoList.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetCityInfoList.fromJson(Map json) { + ColorName? parseColorName(dynamic value) { + if (value == null) return null; + try { + return colorNameValues.map[value.toString()]; + } catch (e) { + print("Unknown color name: $value"); + return null; + } + } + + CategoriesNames? parseCategoriesNames(dynamic value) { + if (value == null) return null; + try { + return CategoriesNames.fromJson(value as Map); + } catch (e) { + print("Error parsing CategoriesNames: $e"); + return null; + } + } + + return GetCityInfoList( + categoriesNames: parseCategoriesNames(json["CategoriesNames"]), + category: json["Category"], + categoryValue: json["CategoryValue"], + cityId: json["CityID"], + cityName: json["CityName"], + cityNameN: json["CityNameN"], + colorName: parseColorName(json["ColorName"]), + createdOn: json["CreatedOn"], + id: json["ID"], + iniceId: json["IniceID"], + isOrderEmpty: json["IsOrderEmpty"], + isValuesReversed: json["IsValuesReversed"], + language: json["Language"], + latitude: json["Latitude"]?.toDouble(), + longitude: json["Longitude"]?.toDouble(), + name: json["Name"], + orderNum: json["OrderNum"], + temperature: json["Temperature"]?.toDouble(), + value: json["Value"], + ); + } + + Map toJson() => { + "CategoriesNames": categoriesNames?.toJson(), + "Category": category, + "CategoryValue": categoryValue, + "CityID": cityId, + "CityName": cityName, + "CityNameN": cityNameN, + "ColorName": colorNameValues.reverse[colorName], + "CreatedOn": createdOn, + "ID": id, + "IniceID": iniceId, + "IsOrderEmpty": isOrderEmpty, + "IsValuesReversed": isValuesReversed, + "Language": language, + "Latitude": latitude, + "Longitude": longitude, + "Name": name, + "OrderNum": orderNum, + "Temperature": temperature, + "Value": value, + }; +} + +class CategoriesNames { + Category1? category1; + Category2? category2; + Category3? category3; + Category4? category4; + Category5? category5; + + CategoriesNames({ + this.category1, + this.category2, + this.category3, + this.category4, + this.category5, + }); + + factory CategoriesNames.fromRawJson(String str) => CategoriesNames.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CategoriesNames.fromJson(Map json) { + T? parseEnum(dynamic value, EnumValues enumValues, String enumName) { + if (value == null) return null; + try { + return enumValues.map[value.toString()]; + } catch (e) { + print("Unknown $enumName value: $value"); + return null; + } + } + + return CategoriesNames( + category1: parseEnum(json["Category1"], category1Values, "Category1"), + category2: parseEnum(json["Category2"], category2Values, "Category2"), + category3: parseEnum(json["Category3"], category3Values, "Category3"), + category4: parseEnum(json["Category4"], category4Values, "Category4"), + category5: parseEnum(json["Category5"], category5Values, "Category5"), + ); + } + + Map toJson() => { + "Category1": category1Values.reverse[category1], + "Category2": category2Values.reverse[category2], + "Category3": category3Values.reverse[category3], + "Category4": category4Values.reverse[category4], + "Category5": category5Values.reverse[category5], + }; +} + +enum Category1 { + AT_EXTREME_RISK, + CATEGORY1_EXTREME, + EXTREME, + POOR +} + +final category1Values = EnumValues({ + "At Extreme Risk": Category1.AT_EXTREME_RISK, + "Extreme\u000d\n": Category1.CATEGORY1_EXTREME, + "Extreme": Category1.EXTREME, + "Poor": Category1.POOR +}); + +enum Category2 { + AT_HIGH_RISK, + CATEGORY2_VERY_HIGH, + FAIR, + VERY_HIGH +} + +final category2Values = EnumValues({ + "At High Risk": Category2.AT_HIGH_RISK, + "Very High\u000d\n": Category2.CATEGORY2_VERY_HIGH, + "Fair": Category2.FAIR, + "Very High": Category2.VERY_HIGH +}); + +enum Category3 { + AT_RISK, + CATEGORY3_HIGH, + GOOD, + HIGH +} + +final category3Values = EnumValues({ + "At Risk": Category3.AT_RISK, + "High\u000d\n": Category3.CATEGORY3_HIGH, + "Good": Category3.GOOD, + "High": Category3.HIGH +}); + +enum Category4 { + CATEGORY4_MODERATE, + MODERATE, + NEUTRAL, + VERY_GOOD +} + +final category4Values = EnumValues({ + "Moderate\u000d\n": Category4.CATEGORY4_MODERATE, + "Moderate": Category4.MODERATE, + "Neutral": Category4.NEUTRAL, + "Very Good": Category4.VERY_GOOD +}); + +enum Category5 { + BENEFICIAL, + CATEGORY5_LOW, + EXCELLENT, + LOW +} + +final category5Values = EnumValues({ + "Beneficial": Category5.BENEFICIAL, + "Low\u000d\n": Category5.CATEGORY5_LOW, + "Excellent": Category5.EXCELLENT, + "Low": Category5.LOW +}); + +enum ColorName { + GREEN, + ORANGE, + RED +} + +final colorNameValues = EnumValues({ + "green": ColorName.GREEN, + "orange": ColorName.ORANGE, + "red": ColorName.RED +}); + +class EnumValues { + Map map; + late Map reverseMap; + + EnumValues(this.map); + + Map get reverse { + reverseMap = map.map((k, v) => MapEntry(v, k)); + return reverseMap; + } +} diff --git a/lib/features/weather/weather_repo.dart b/lib/features/weather/weather_repo.dart new file mode 100644 index 00000000..26bf0a32 --- /dev/null +++ b/lib/features/weather/weather_repo.dart @@ -0,0 +1,72 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class WeatherRepo { + Future>>> getCityInfo(); +} + +class WeatherRepoImp implements WeatherRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + WeatherRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getCityInfo() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + WEATHER_INDICATOR, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + loggerService.logInfo("Weather API Response: $response"); + final list = response['GetCityInfo_List']; + + if (list == null || list.isEmpty) { + loggerService.logInfo("Weather API returned empty or null list"); + // Return empty list if no city info + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: [], + ); + return; + } + + loggerService.logInfo("Parsing ${list.length} city info items"); + final cityInfoList = list.map((item) => GetCityInfoList.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: cityInfoList, + ); + } catch (e) { + loggerService.logError("Error parsing weather data: $e"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/weather/weather_view_model.dart b/lib/features/weather/weather_view_model.dart new file mode 100644 index 00000000..0e3a3285 --- /dev/null +++ b/lib/features/weather/weather_view_model.dart @@ -0,0 +1,55 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class WeatherMonitorViewModel extends ChangeNotifier { + WeatherRepo weatherRepo; + ErrorHandlerService errorHandlerService; + LocationUtils locationUtils; + + WeatherMonitorViewModel({required this.weatherRepo, required this.errorHandlerService, required this.locationUtils}); + + final List _cityInfoList = []; + bool isLoading = false; + + List get cityInfoList => _cityInfoList; + + Future fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isLoading = true; + _cityInfoList.clear(); + + locationUtils.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) async { + final result = await weatherRepo.getCityInfo(); + result.fold( + (failure) async { + isLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isLoading = false; + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } + } else if (apiResponse.messageStatus == 1) { + final cities = apiResponse.data ?? []; + _cityInfoList.addAll(cities); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + }); + } +} diff --git a/lib/main.dart b/lib/main.dart index e9b599c9..6f086c34 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.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/utils.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; @@ -242,6 +243,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ) , + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index ef3b0c94..3db1bfdf 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -7,7 +7,6 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; 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/dependencies.dart'; -import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -196,11 +195,13 @@ class _AppointmentDetailsPageState extends State { }, onError: (err) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, + showCommonBottomSheetWithoutHeight(context, child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, - title: "", isCloseButtonVisible: true, isDismissible: false, isFullScreen: false); + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false); }); }), callBackFunc: () {}, @@ -359,22 +360,24 @@ class _AppointmentDetailsPageState extends State { ], ), SizedBox(height: 8.h), - bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( - direction: Axis.horizontal, - spacing: 8.w, - runSpacing: 8.h, - children: [ - AppCustomChipWidget( - labelText: bookAppointmentsVM.isAppointmentNearestGateLoading - ? "Floor: Ground Floor" + bookAppointmentsViewModel.appointmentNearestGateResponseModel != null + ? Wrap( + direction: Axis.horizontal, + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" : "${LocaleKeys.floor.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - AppCustomChipWidget( - labelText: + AppCustomChipWidget( + labelText: "${LocaleKeys.nearestGate.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - ], - ) : SizedBox.shrink(), + ], + ) + : SizedBox.shrink(), ], ), ), @@ -405,53 +408,40 @@ class _AppointmentDetailsPageState extends State { // activeTrackColor: AppColors.successColor.withValues(alpha: .15), value: widget.patientAppointmentHistoryResponseModel.hasReminder!, onChanged: (newValue) async { - CalenderUtilsNew calender = CalenderUtilsNew.instance; - bool isEventAddedOrRemoved = false; - if(newValue == true){ - DateTime startDate = DateTime.now(); - DateTime endDate = DateUtil.convertStringToDate(widget - .patientAppointmentHistoryResponseModel.appointmentDate); - BottomSheetUtils().showReminderBottomSheet( - context, - endDate, - widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", - "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", - "", - "", - title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - description: - "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - onSuccess: () { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - isMultiAllowed: true, - onMultiDateSuccess: (int selectedIndex) async { - isEventAddedOrRemoved = await calender.createOrUpdateEvent( - title: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - description: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - scheduleDateTime: DateUtil.convertStringToDate(widget - .patientAppointmentHistoryResponseModel.appointmentDate), - eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - location: '', - reminderMinutes: selectedIndex - ); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); - }, - ); - }else { - isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); - } - - + await myAppointmentsViewModel.toggleReminder( + context: context, + shouldCreateReminder: newValue, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (result) { + showCommonBottomSheet( + context, + child: Utils.getSuccessWidget( + loadingText: newValue ? LocaleKeys.reminderSuccess.tr(context: context) : LocaleKeys.reminderCancelSuccess.tr(context: context), + ), + callBackFunc: (str) {}, + title: "", + isCloseButtonVisible: false, + isDismissible: true, + isFullScreen: false, + isAutoDismiss: true, + height: ResponsiveExtension.screenHeight * 0.3, + ); + }, + onError: (error) { + setState(() { + // Revert the switch if there was an error + widget.patientAppointmentHistoryResponseModel.hasReminder = !newValue; + }); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Utils.getErrorWidget(loadingText: error), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); }, ), ], @@ -471,106 +461,116 @@ class _AppointmentDetailsPageState extends State { ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context)); - await labViewModel.getLabResultsByAppointmentNo( - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, - clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, - projectName: widget.patientAppointmentHistoryResponseModel.projectName!, - appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - }), - MedicalFileCard( - label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context)); - await radiologyViewModel.getPatientRadiologyOrdersByAppointment( - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - }), - MedicalFileCard( - label: LocaleKeys.prescriptions.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.prescription_item_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context)); - await prescriptionsViewModel.getPrescriptionDetails( - getPrescriptionRequestModel(), - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - if (val.data.isNotEmpty) { - PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( - doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, - doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, - appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, - clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, - decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, - name: widget.patientAppointmentHistoryResponseModel.projectName, - isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - setupID: widget.patientAppointmentHistoryResponseModel.setupID, - isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, - dischargeNo: 0, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context)); + await labViewModel.getLabResultsByAppointmentNo( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, + clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, + projectName: widget.patientAppointmentHistoryResponseModel.projectName!, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, ); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage( - isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), - ), + }, + ); + }), + MedicalFileCard( + label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.radiology_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context)); + await radiologyViewModel.getPatientRadiologyOrdersByAppointment( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, ); - } else { + }, + ); + }), + MedicalFileCard( + label: LocaleKeys.prescriptions.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.prescription_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context)); + await prescriptionsViewModel.getPrescriptionDetails( + getPrescriptionRequestModel(), + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (val.data.isNotEmpty) { + PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + dischargeNo: 0, + ); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), @@ -578,40 +578,29 @@ class _AppointmentDetailsPageState extends State { isFullScreen: false, isCloseButtonVisible: true, ); - } - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - // Navigator.of(context).push( - // CustomPageRoute( - // page: VaccineListPage(), - // ), - // ); - }), - MedicalFileCard( - label: LocaleKeys.doctorResponses.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.ask_doctor_medical_file_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - getIt.get().initAskDoctorViewModel(); - getIt.get().getDoctorResponses(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorResponsePage(), - ), - ); + }, + ); + // Navigator.of(context).push( + // CustomPageRoute( + // page: VaccineListPage(), + // ), + // ); + }), + MedicalFileCard( + label: LocaleKeys.doctorResponses.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ask_doctor_medical_file_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + getIt.get().initAskDoctorViewModel(); + getIt.get().getDoctorResponses(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorResponsePage(), + ), + ); }), myAppointmentsVM.isAppointmentRatedResponseLoading ? MedicalFileCard( @@ -882,8 +871,7 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || - widget.patientAppointmentHistoryResponseModel.nextAction == 20) + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -891,10 +879,7 @@ class _AppointmentDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), - Utils.getPaymentAmountWithSymbol( - widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), - AppColors.blackColor, - 13, + Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], ), @@ -902,9 +887,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: LocaleKeys.upcomingPaymentNow - .tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), + Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), ], ), @@ -919,10 +902,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.getPaymentAmountWithSymbol( - widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), - AppColors.blackColor, - 17, + Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), ], ), @@ -945,11 +925,11 @@ class _AppointmentDetailsPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 50.h, - icon: AppAssets.add_icon, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 50.h, + icon: AppAssets.add_icon, iconColor: Colors.white, iconSize: 18.h, ).paddingSymmetrical(16.h, 24.h) @@ -959,8 +939,7 @@ class _AppointmentDetailsPageState extends State { handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white, fontSize: 16.f, fontWeight: FontWeight.w600, @@ -1121,8 +1100,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); Navigator.of(context).pop(); - }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, - isSuccessDialog: true); + }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true); }); // LoaderBottomSheet.hideLoader(); case 15: diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 689e6cf3..6a1838f9 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -90,12 +90,14 @@ class _ReviewAppointmentPageState extends State { .toString() .toText16(isBold: true, maxlines: 1), SizedBox(width: 12.w), - (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty )? Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", - width: 20.h, - height: 15.h, - fit: BoxFit.cover, - ) : SizedBox.shrink(), + (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) + ? Image.network( + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + width: 20.h, + height: 15.h, + fit: BoxFit.cover, + ) + : SizedBox.shrink(), ], ), SizedBox(height: 2.h), @@ -162,7 +164,9 @@ class _ReviewAppointmentPageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), - AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), + AppCustomChipWidget( + labelText: + "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ], ), ], @@ -191,19 +195,19 @@ class _ReviewAppointmentPageState extends State { bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( direction: Axis.horizontal, - spacing: 8.w, - runSpacing: 8.h, - children: [ - AppCustomChipWidget( - labelText: bookAppointmentsVM.isAppointmentNearestGateLoading - ? "Floor: Ground Floor" + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" : "${LocaleKeys.floor.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - AppCustomChipWidget( - labelText: + AppCustomChipWidget( + labelText: "${LocaleKeys.nearestGate.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - ], + ], ) : SizedBox.shrink(), ], @@ -329,23 +333,16 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr()); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), - callBackFunc: () { - bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); - bookAppointmentsViewModel.setIsLiveCareSchedule(false); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - isFullScreen: false, - isCloseButtonVisible: false, - isAutoDismiss: true - ); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () { + bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true); }); }); } else { @@ -368,23 +365,48 @@ class _ReviewAppointmentPageState extends State { }, onSuccess: (apiResp) async { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), - callBackFunc: () { - bookAppointmentsViewModel.setIsLiveCareSchedule(false); - bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () async { + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); + bool newValue = true; + await myAppointmentsViewModel.toggleReminder( + context: context, + shouldCreateReminder: newValue, + patientAppointmentHistoryResponseModel: apiResp.patientAppointmentHistoryResponseModel, + onSuccess: (result) { + showCommonBottomSheet( + context, + child: Utils.getSuccessWidget( + loadingText: newValue ? LocaleKeys.reminderSuccess.tr(context: context) : LocaleKeys.reminderCancelSuccess.tr(context: context), + ), + callBackFunc: (str) { + Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + }, + title: "", + isCloseButtonVisible: false, + isDismissible: true, + isFullScreen: false, + isAutoDismiss: true, + height: ResponsiveExtension.screenHeight * 0.3, + ); + }, + onError: (error) { + setState(() { + apiResp.patientAppointmentHistoryResponseModel.hasReminder = !newValue; + }); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Utils.getErrorWidget(loadingText: error), + callBackFunc: () { + Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, - isFullScreen: false, - isCloseButtonVisible: false, - isAutoDismiss: true - ); + ); + }, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true); }); }); } diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 770a5cbe..c92d47e5 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -6,6 +6,7 @@ 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/enums.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -19,6 +20,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_s import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_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/book_appointment/book_appointment_page.dart'; @@ -47,47 +49,46 @@ import '../../core/dependencies.dart' show getIt; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; -class ServicesPage extends StatelessWidget { +class ServicesPage extends StatefulWidget { bool showBackIcon; ServicesPage({super.key, this.showBackIcon = true}); + @override + State createState() => _ServicesPageState(); +} + +class _ServicesPageState extends State { late BloodDonationViewModel bloodDonationViewModel; + late MedicalFileViewModel medicalFileViewModel; + late WeatherMonitorViewModel weatherVM; + late final List hmgServices = [ HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { // if (getIt.get().isAuthenticated) { - // getIt.get().flushData(); - // getIt.get().getTransportationOrders( - // showLoader: false, - // ); - // getIt.get().getRRTOrders( - // showLoader: false, - // ); - Navigator.of(getIt.get().navigatorKey.currentContext!).push( - CustomPageRoute( - page: EmergencyServicesPage(), - settings: const RouteSettings(name: '/EmergencyServicesPage'), - ), - ); + // getIt.get().flushData(); + // getIt.get().getTransportationOrders( + // showLoader: false, + // ); + // getIt.get().getRRTOrders( + // showLoader: false, + // ); + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute( + page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), + ), + ); // } else { // await getIt.get().onLoginPressed(); // } }), - HmgServicesComponentModel( - 11, - LocaleKeys.bookAppointment.tr(), - "", - AppAssets.appointment_calendar_icon, - bgColor: AppColors.bookAppointment, - true, - route: null, - onTap: () { - getIt.get().onTabChanged(0); - Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); - } - ), + HmgServicesComponentModel(11, LocaleKeys.bookAppointment.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { + getIt.get().onTabChanged(0); + Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); + }), HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); @@ -164,33 +165,31 @@ class ServicesPage extends StatelessWidget { } }), HmgServicesComponentModel( - 3, - LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), - "".needTranslation, - AppAssets.blood_donation_icon, - bgColor: AppColors.bloodDonationCardColor, - true, - route: null, - onTap: () { - if (getIt.get().isAuthenticated) { - Navigator.of(getIt().navigatorKey.currentContext!).push( - CustomPageRoute( - page: BloodDonationPage(), - ), - ); - } else { - Navigator.of(getIt().navigatorKey.currentContext!).push( - CustomPageRoute( - page: ServiceInfoPage( - serviceName: LocaleKeys.bloodDonation.tr(), - serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(), - serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(), - serviceImage: AppAssets.bloodDonationService), - ), - ); - } + 3, + LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), + "".needTranslation, + AppAssets.blood_donation_icon, + bgColor: AppColors.bloodDonationCardColor, + true, + route: null, onTap: () { + if (getIt.get().isAuthenticated) { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: BloodDonationPage(), + ), + ); + } else { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: ServiceInfoPage( + serviceName: LocaleKeys.bloodDonation.tr(), + serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(), + serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(), + serviceImage: AppAssets.bloodDonationService), + ), + ); } - ), + }), HmgServicesComponentModel( 113, LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!), @@ -375,21 +374,100 @@ class ServicesPage extends StatelessWidget { ), ]; + @override + void initState() { + // TODO: implement initState + super.initState(); + weatherVM = getIt(); + weatherVM.fetchCityInfoList(); + } + + + @override Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); medicalFileViewModel = Provider.of(context); - hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.exploreServices.tr(), - isLeading: showBackIcon, + isLeading: widget.showBackIcon, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), + child: Column( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: "assets/images/svg/cloudyweather.svg", width: 64.w, height: 64.w).onPress(() { + // locationUtils?.getCurrentLocation(); + print(getIt.get().userLat); + print(getIt.get().userLong); + }), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "August 19, 2025 - Tuesday, 11:25 ".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox( + height: 4.w, + ), + "Cloudy 38oC".toText16(isBold: true, weight: FontWeight.w600), + SizedBox( + height: 4.w, + ), + "Health tips based on current weather".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500) + ], + ), + ], + ), + SizedBox(height: 16.w), + Container( + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: 10.r, + hasShadow: false, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: "assets/images/svg/morning.svg", width: 24.w, height: 24.h), + SizedBox(width: 4.w), + DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true), + ], + ), + SizedBox(width: 25.w), + DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true), + SizedBox(width: 25.w), + Row( + children: [ + Utils.buildSvgWithAssets(icon: "assets/images/svg/evening.svg", width: 24.w, height: 24.h), + SizedBox(width: 4.w), + DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true), + ], + ), + ], + ).expanded, + ).paddingSymmetrical(0.w, 8.w) + ], + ), + ), + ) + ], + ).paddingSymmetrical(24.w, 0.w), SizedBox(height: 16.h), LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), @@ -440,173 +518,176 @@ class ServicesPage extends StatelessWidget { ), ), SizedBox(height: 24.h), - getIt.get().isAuthenticated ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - height: 183.h, - width: 183.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: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], - ), - Spacer(), - getIt.get().isAuthenticated - ? Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), isExpanded: false, letterSpacing: -1) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }) - : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 24.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); - } else { - await getIt.get().onLoginPressed(); - } - }), - ), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - height: 183.h, - width: 183.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: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], + getIt.get().isAuthenticated + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + height: 183.h, + width: 183.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: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), + isExpanded: false, letterSpacing: -1) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) + : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 24.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: LocaleKeys.recharge.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + } else { + await getIt.get().onLoginPressed(); + } + }), ), - Spacer(), - getIt.get().isAuthenticated - ? Wrap( - spacing: -12.h, - // runSpacing: 0.h, - children: [ - Utils.buildImgWithAssets( - icon: AppAssets.babyGirlImg, - height: 32.h, - width: 32.w, - border: 1, - fit: BoxFit.contain, - borderRadius: 50.r, - ), - Utils.buildImgWithAssets( - icon: AppAssets.femaleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - Utils.buildImgWithAssets( - icon: AppAssets.maleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - ], - ) - : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.add_icon, - iconSize: 24.h, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - text: LocaleKeys.addMember.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalFileViewModel.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), - onVerificationPress: () { - medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + height: 183.h, + width: 183.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, ), - ); - } else { - await getIt.get().onLoginPressed(); - } - }), - ), - ), - ], - ).paddingSymmetrical(24.w, 0), - ], - ) : SizedBox(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Wrap( + spacing: -12.h, + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 32.h, + width: 32.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.maleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], + ) + : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.add_icon, + iconSize: 24.h, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: LocaleKeys.addMember.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } + }), + ), + ), + ], + ).paddingSymmetrical(24.w, 0), + ], + ) + : SizedBox(), SizedBox(height: 24.h), LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h),