Compare commits

...

33 Commits

Author SHA1 Message Date
Sultan khan 0ddaa6899d Merge branch 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New into dev_sultan
* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New:
  fixed parking qr
  added parking part
  added monthly report
  added monthly report
  added monthly report
  fix error
  monthly report
  fix toggle
  fix toggle
  active medication
  active medication
  active medication
2 months ago
Sultan khan 42222804fd fix graph dot and bp graph. 2 months ago
Haroon6138 b5753769b7 Merge pull request 'fatima' (#152) from fatima into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/152
2 months ago
Fatimah.Alshammari 250af1506c Merge branch 'master' into fatima
# Conflicts:
#	lib/core/dependencies.dart
#	lib/presentation/medical_file/medical_file_page.dart
#	lib/routes/app_routes.dart
#	lib/services/dialog_service.dart
#	lib/theme/colors.dart
2 months ago
Haroon6138 aee496b7c3 Merge pull request 'dev_sultan' (#151) from dev_sultan into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/151
2 months ago
Sultan khan 16da0f4d0a Merge branch 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New into dev_sultan 2 months ago
Sultan khan 8ee0e3b4ea no message 2 months ago
Haroon6138 96ebc3c7e9 Merge pull request 'dev_aamir' (#149) from dev_aamir into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/149
2 months ago
aamir-csol f62c7c9b20 Merge branch 'master' into dev_aamir 2 months ago
aamir-csol c11e859df1 search doctor by name filter. 2 months ago
Fatimah.Alshammari 20e4f82df3 Merge branch 'master' into fatima
# Conflicts:
#	lib/core/dependencies.dart
#	lib/main.dart
#	lib/presentation/hmg_services/services_page.dart
#	lib/presentation/medical_file/medical_file_page.dart
#	lib/routes/app_routes.dart
2 months ago
Fatimah.Alshammari 8ae81d2b24 fixed parking qr 2 months ago
Fatimah.Alshammari 11de01e079 Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/presentation/hmg_services/services_page.dart
#	lib/routes/app_routes.dart
#	lib/theme/colors.dart
2 months ago
Fatimah.Alshammari 62bb667cb8 added parking part 2 months ago
Fatimah.Alshammari f3674ba06f Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/core/dependencies.dart
#	lib/presentation/hmg_services/services_page.dart
#	lib/routes/app_routes.dart
2 months ago
Fatimah.Alshammari a53602dc8c added monthly report 2 months ago
Fatimah.Alshammari 7a03242396 added monthly report 2 months ago
Fatimah.Alshammari 68f044de52 added monthly report 2 months ago
Fatimah.Alshammari c9de23347a fix error 3 months ago
Fatimah.Alshammari 190492a191 Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/routes/app_routes.dart
3 months ago
Fatimah.Alshammari 3d8daeb58d Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/core/app_assets.dart
#	lib/core/dependencies.dart
#	lib/core/utils/calendar_utils.dart
#	lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart
#	lib/main.dart
#	lib/presentation/hmg_services/services_page.dart
#	lib/presentation/hmg_services/services_view.dart
#	lib/presentation/medical_file/medical_file_page.dart
#	lib/routes/app_routes.dart
#	lib/theme/colors.dart
#	pubspec.yaml
3 months ago
Fatimah.Alshammari 0b3fea230f monthly report 3 months ago
Fatimah.Alshammari 86a91ab383 Merge branch 'refs/heads/master' into fatima 3 months ago
Fatimah.Alshammari 89ff10f506 Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/core/dependencies.dart
#	lib/main.dart
#	lib/theme/colors.dart
3 months ago
Fatimah.Alshammari 8191852f4d Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/core/dependencies.dart
#	lib/main.dart
3 months ago
Fatimah.Alshammari e17d3cde87 fix toggle 3 months ago
Fatimah.Alshammari 77242825bb fix toggle 3 months ago
Fatimah.Alshammari 27ad4e0764 Merge branch 'refs/heads/master' into fatima
# Conflicts:
#	lib/core/utils/utils.dart
#	lib/presentation/emergency_services/call_ambulance/tracking_screen.dart
#	lib/presentation/home/landing_page.dart
4 months ago
Fatimah.Alshammari eae16eec44 active medication 4 months ago
Fatimah.Alshammari 3f123d12d6 Merge remote-tracking branch 'refs/remotes/origin/master' into fatima
# Conflicts:
#	lib/core/api_consts.dart
#	lib/core/utils/utils.dart
#	lib/main.dart
4 months ago
Fatimah.Alshammari de8c7bc605 active medication 4 months ago
Fatimah.Alshammari cb56503622 Merge remote-tracking branch 'refs/remotes/origin/master' into fatima 5 months ago
Fatimah.Alshammari e8319a0d3f active medication 5 months ago

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -207,7 +207,7 @@ var GET_APPOINTMENT_DETAILS_BY_NO = 'Services/MobileNotifications.svc/REST/GetAp
var NEW_RATE_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate";
var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate";
var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID';
//var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID';
//URL to get clinic list
var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized";
@ -785,6 +785,11 @@ class ApiConsts {
static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus';
static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile';
static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus';
static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID';
static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions';
static final String getMonthlyReports = 'Services/Patients.svc/Rest/UpdatePateintHealthSummaryReport';
static final String updatePatientEmail = 'Services/Patients.svc/Rest/UpdatePateintEmail';
static final String getQrParkingDetails = 'Services/SWP.svc/REST/GetQRParkingByID';
// Ancillary Order Apis
static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList';

@ -349,3 +349,4 @@ class AppAnimations {
static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json';
}

@ -10,6 +10,8 @@ import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart';
import 'package:hmg_patient_app_new/features/common/common_repo.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
@ -44,6 +46,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
@ -55,6 +58,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.da
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@ -68,8 +72,13 @@ import 'package:local_auth/local_auth.dart';
import 'package:logger/web.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../features/qr_parking/qr_parking_view_model.dart';
import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import '../features/active_prescriptions/active_prescriptions_repo.dart';
import '../features/monthly_reports/terms_conditions_repo.dart';
import '../features/monthly_reports/terms_conditions_view_model.dart';
GetIt getIt = GetIt.instance;
class AppDependencies {
@ -147,6 +156,19 @@ class AppDependencies {
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsRepo>(() => ActivePrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<TermsConditionsRepo>(() => TermsConditionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerFactory<TermsConditionsViewModel>(() => TermsConditionsViewModel(termsConditionsRepo: getIt<TermsConditionsRepo>(), errorHandlerService: getIt<ErrorHandlerService>(),
),);
getIt.registerLazySingleton<MonthlyReportsRepo>(() => MonthlyReportsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<QrParkingRepo>(() => QrParkingRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerFactory<QrParkingViewModel>(
() => QrParkingViewModel(
qrParkingRepo: getIt<QrParkingRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
cacheService: getIt<CacheService>(),
),
);
// ViewModels
// Global/shared VMs LazySingleton
@ -265,7 +287,8 @@ class AppDependencies {
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
//commenting this because its already define there was on run time error because of this.
// getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<MonthlyReportViewModel>(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(
@ -274,5 +297,19 @@ class AppDependencies {
navServices: getIt(),
));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsViewModel>(
() => ActivePrescriptionsViewModel(
errorHandlerService: getIt(),
activePrescriptionsRepo: getIt()
),
);
getIt.registerFactory<QrParkingViewModel>(
() => QrParkingViewModel(
qrParkingRepo: getIt<QrParkingRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
cacheService: getIt<CacheService>(),
),
);
}
}

@ -0,0 +1,316 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/widgets.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/permission_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:jiffy/jiffy.dart';
import 'package:manage_calendar_events/manage_calendar_events.dart' as ios;
import 'package:permission_handler/permission_handler.dart';
import 'package:timezone/data/latest.dart' as tzl;
final DeviceCalendarPlugin deviceCalendarPlugin = DeviceCalendarPlugin();
final ios.CalendarPlugin _myPlugin = ios.CalendarPlugin();
class CalendarUtils {
static Completer<CalendarUtils>? _completer;
dynamic get writableCalendars => calendars.firstWhere((c) => !c.isReadOnly!);
dynamic calendars;
CalendarUtils._(this.calendars);
// static Future<CalendarUtils> getInstance() async {
// if (_completer == null) {
// _completer = Completer<CalendarUtils>();
// print(_completer!.isCompleted);
// try {
// final dynamic calendarsResult;
// if (Platform.isIOS) {
// calendarsResult = await _myPlugin.getCalendars();
// if (!_completer!.isCompleted) {
// _completer?.complete(CalendarUtils._(await calendarsResult!));
// }
// } else {
// calendarsResult = await deviceCalendarPlugin.retrieveCalendars();
// if (!_completer!.isCompleted) {
// _completer?.complete(CalendarUtils._(await calendarsResult.data!));
// }
// }
// } on Exception catch (e) {
// if (!_completer!.isCompleted) {
// _completer!.completeError(e);
// }
// }
// }
// return _completer!.future;
// }
static Future<CalendarUtils> getInstance() async {
tzl.initializeTimeZones();
if (_completer != null) {
return _completer!.future;
}
_completer = Completer<CalendarUtils>();
try {
final dynamic calendarsResult;
if (Platform.isIOS) {
calendarsResult = await _myPlugin.getCalendars();
_completer!.complete(CalendarUtils._(calendarsResult));
} else {
calendarsResult = await deviceCalendarPlugin.retrieveCalendars();
_completer!.complete(CalendarUtils._(calendarsResult.data));
}
} catch (e) {
_completer!.completeError(e);
}
return _completer!.future;
}
Future createOrUpdateEvents({List<DateTime>? scheduleList, String? title, String? description, List<DateTime>? scheduleDateTime, List<DayOfWeek>? daysOfWeek}) async {
tzl.initializeTimeZones();
List<Event> events = [];
Location _currentLocation;
if (DateTime.now().timeZoneName == "+04")
_currentLocation = getLocation('Asia/Dubai');
else
_currentLocation = getLocation('Asia/Riyadh');
scheduleDateTime!.forEach((element) {
RecurrenceRule recurrenceRule = RecurrenceRule(
// RecurrenceFrequency.Daily,
// daysOfWeek: daysOfWeek,
// endDate: element,
until: element, frequency: Frequency.daily,
);
//added byAamir Tz Time
Event event = Event(writableCalendars!.id,
recurrenceRule: recurrenceRule,
start: TZDateTime.from(element, _currentLocation),
end: TZDateTime.from(element.add(Duration(minutes: 30)), _currentLocation),
title: title,
description: description);
events.add(event);
});
events.forEach((element) {
deviceCalendarPlugin.createOrUpdateEvent(element);
});
}
Future createOrUpdateEvent({required String title, required String description, required String location, DateTime? scheduleDateTime, String? eventId}) async {
RecurrenceRule recurrenceRule = RecurrenceRule(
// RecurrenceFrequency.Daily,
// daysOfWeek: daysOfWeek,
// endDate: scheduleDateTime,
until: scheduleDateTime, frequency: Frequency.daily,
);
Location _currentLocation;
// if (DateTime.now().timeZoneName == "+04")
// _currentLocation = getLocation('Asia/Dubai');
// else
_currentLocation = getLocation('Asia/Riyadh');
TZDateTime scheduleDateTimeUTZ = TZDateTime.from(scheduleDateTime!, _currentLocation);
print("writableCalendars-name: " + writableCalendars.name);
print("writableCalendars-Id: " + writableCalendars.id);
print("writableCalendarsToString: " + writableCalendars.toString());
print("writableCalendarsToString: " + writableCalendars!.id!);
Event event = Event(
writableCalendars!.id,
start: scheduleDateTimeUTZ,
end: scheduleDateTimeUTZ.add(Duration(minutes: 30)),
title: title,
description: description,
);
ios.CalendarEvent iosCalEvent =
ios.CalendarEvent(location: location, startDate: scheduleDateTimeUTZ, endDate: scheduleDateTimeUTZ.add(Duration(minutes: 30)), title: title, description: description, isAllDay: false);
if (Platform.isAndroid) {
Result<bool> result = await deviceCalendarPlugin.hasPermissions();
print(result);
await deviceCalendarPlugin.createOrUpdateEvent(event).catchError((e) {
print("catchError " + e.toString());
}).whenComplete(() {
print("whenComplete Calender ID " + eventId!);
});
} else {
await _myPlugin.createEvent(calendarId: writableCalendars.id!, event: iosCalEvent).catchError((e) {
print("catchError " + e.toString());
}).whenComplete(() {
print("whenComplete Calender ID iOS " + eventId!);
});
}
}
deleteEvent(String _calendarId, String _eventId) async {
if (Platform.isIOS) {
await _myPlugin.deleteEvent(calendarId: _calendarId, eventId: _eventId);
} else {
await deviceCalendarPlugin.deleteEvent(_calendarId, _eventId);
}
}
Future retrieveEvents(
String calendarId,
RetrieveEventsParams retrieveEventsParams,
) async {
if (Platform.isIOS) {
return await _myPlugin.getEvents(calendarId: calendarId);
} else {
return await deviceCalendarPlugin.retrieveEvents(calendarId, retrieveEventsParams);
}
}
Future createCalendar(
String calendarName, {
Color? calendarColor,
String? localAccountName,
}) async {
return await deviceCalendarPlugin.createCalendar(calendarName, calendarColor: calendarColor, localAccountName: localAccountName);
}
}
Future<Map<Permission, PermissionStatus>> requestPermissions() async {
var permissionResults = [Permission.calendarFullAccess].request();
return permissionResults;
}
showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted,
{required Function() onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool isMultiAllowed = false}) async {
if (Platform.isAndroid) {
if (await PermissionService.isCalendarPermissionEnabled()) {
_showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted,
onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed);
} else {
// Utils.showPermissionConsentDialog(context, TranslationBase.of(context).calendarPermission, () async {
// if (await Permission.calendarFullAccess.request().isGranted) {
// _showReminderDialog(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted,
// onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed);
// }
// });
}
} else {
if (await Permission.calendarWriteOnly.request().isGranted) {
if (await Permission.calendarFullAccess.request().isGranted) {
_showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted,
onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed);
}
}
}
}
Future<void> _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted,
{required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async {
showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder".needTranslation, child: PrescriptionReminderView(
setReminder: (int value) async {
if (!isMultiAllowed!) {
if (onMultiDateSuccess == null) {
CalendarUtils calendarUtils = await CalendarUtils.getInstance();
await calendarUtils.createOrUpdateEvent(
title: title ?? "You have appointment with Dr. ".needTranslation + doctorName,
description: description ?? "At " + appoDateFormatted + " " + appoTimeFormatted,
scheduleDateTime: dateTime,
eventId: eventId,
location: '');
onSuccess();
}
} else {
onMultiDateSuccess!(value);
}
},
), callBackFunc: () {}, isFullScreen: false);
}
setCalender(BuildContext context,
{required String eventId, required int selectedMinutes, int? frequencyNumber, required int days, required String orderDate, required String itemDescriptionN, required String route}) async {
DateTime actualDate = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0);
frequencyNumber ??= 2; //Some time frequency number is null so by default will be 2
int remainingDays = days - (Jiffy.parseFromDateTime(DateTime.now()).diff(Jiffy.parseFromDateTime(DateUtil.convertStringToDate(orderDate)), unit: Unit.day) as int);
if (remainingDays.isNegative) {
getIt.get<DialogService>().showErrorBottomSheet(message: "Prescription date has been already passed you can not add a reminder for this prescription.");
return;
}
CalendarUtils calendarUtils = await CalendarUtils.getInstance();
try {
for (int i = 0; i < remainingDays; i++) {
//event for number of days.
for (int j = 0; j < frequencyNumber; j++) {
// event for number of times per day.
if (j != 0) {
actualDate.add(new Duration(hours: 8)); // 8 hours addition for daily dose.
}
//Time subtraction from actual reminder time. like before 30, or 1 hour.
actualDate = Jiffy.parseFromDateTime(actualDate).subtract(minutes: selectedMinutes).dateTime;
calendarUtils.createOrUpdateEvent(
title: "$itemDescriptionN} Prescription Reminder",
description: "$itemDescriptionN $frequencyNumber $route ",
scheduleDateTime: actualDate,
eventId: eventId + (i.toString() + j.toString()),
location: '', //event id with varitions
);
print("Creating event #$j for day $i$actualDate");
actualDate = DateTime(actualDate.year, actualDate.month, actualDate.day, 8, 0);
}
actualDate = Jiffy.parseFromDateTime(actualDate).add(days: 1).dateTime;
}
} catch (ex) {
getIt.get<DialogService>().showErrorBottomSheet(message: "catch:$ex");
}
}
Future<void> checkAndRemove(hasReminder, {bool delete = false, String itemDescriptionN = ""}) async {
final ios.CalendarPlugin _myPlugin = ios.CalendarPlugin();
CalendarUtils calendarUtils = await CalendarUtils.getInstance();
DateTime startEventsDate = Jiffy.parseFromDateTime(DateTime.now()).subtract(days: 30).dateTime;
DateTime endEventsDate = Jiffy.parseFromDateTime(DateTime.now()).add(days: 120).dateTime;
RetrieveEventsParams params = RetrieveEventsParams(startDate: startEventsDate, endDate: endEventsDate);
if (calendarUtils.calendars != null) {
if (Platform.isAndroid) {
await processEvents(calendarUtils.calendars, calendarUtils, params, delete, itemDescriptionN, hasReminder);
} else {
List<ios.Calendar>? iosCalendars = await _myPlugin.getCalendars();
if (iosCalendars != null) {
await processEvents(iosCalendars.map((cal) => Calendar(id: cal.id, name: cal.name, accountName: cal.accountName)).toList(), calendarUtils, params, delete, itemDescriptionN, hasReminder);
}
}
}
}
Future<void> processEvents(List<Calendar> calendars, calendarUtils, params, delete, String itemDescriptionN, hasReminder) async {
for (var calendar in calendars) {
Result<UnmodifiableListView<Event>> events = await calendarUtils.retrieveEvents(calendar.id!, params);
for (var event in events.data!) {
if (event.title!.contains(itemDescriptionN)) {
if (delete) {
await calendarUtils.deleteEvent(calendar, event);
// AppToast.showSuccessToast(message: TranslationBase.of(context).reminderCancelSuccess);
hasReminder = false;
} else {
hasReminder = false;
// setState(() {
// hasReminder = true;
// });
}
}
}
}
}

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:device_calendar_plus/device_calendar_plus.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:jiffy/jiffy.dart' show Jiffy;
import 'package:manage_calendar_events/manage_calendar_events.dart' hide Calendar;
class CalenderUtilsNew {
final DeviceCalendar calender = DeviceCalendar.instance;

@ -0,0 +1,65 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart';
import '../../core/api/api_client.dart';
import '../../core/api_consts.dart';
import '../../core/common_models/generic_api_model.dart';
import '../../core/exceptions/api_failure.dart';
import '../../services/logger_service.dart';
abstract class ActivePrescriptionsRepo {
Future<Either<Failure, GenericApiModel<List<ActivePrescriptionsResponseModel>>>> getActivePrescriptionsDetails();
}
class ActivePrescriptionsRepoImp implements ActivePrescriptionsRepo {
final ApiClient apiClient;
final LoggerService loggerService;
ActivePrescriptionsRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<ActivePrescriptionsResponseModel>>>> getActivePrescriptionsDetails() async
{
try {
GenericApiModel<List<ActivePrescriptionsResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getActivePrescriptionsDetails,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
var list = response['List_ActiveGetPrescriptionReportByPatientID'];
var res = list
.map<ActivePrescriptionsResponseModel>(
(item) => ActivePrescriptionsResponseModel.fromJson(item))
.toList();
apiResponse = GenericApiModel<List<ActivePrescriptionsResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
// data: response,
data: res
);
return apiResponse;
} catch (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()));
}
}
}

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class ActivePrescriptionsViewModel extends ChangeNotifier {
late ActivePrescriptionsRepo activePrescriptionsRepo;
late ErrorHandlerService errorHandlerService;
List<ActivePrescriptionsResponseModel> activePrescriptionsDetailsList = [];
ActivePrescriptionsViewModel({
required this.activePrescriptionsRepo,
required this.errorHandlerService,
});
Future<void> getActiveMedications({
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
final result =
await activePrescriptionsRepo.getActivePrescriptionsDetails();
result.fold(
(failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 1) {
activePrescriptionsDetailsList = apiResponse.data ?? [];
notifyListeners();
if (onSuccess != null) onSuccess(apiResponse.data);
}
},
);
}
DateTime parseDate(String? date) {
if (date == null) return DateTime.now();
final regex = RegExp(r"\/Date\((\d+)([+-]\d+)?\)\/");
final match = regex.firstMatch(date);
if (match != null) {
final millis = int.parse(match.group(1)!);
return DateTime.fromMillisecondsSinceEpoch(millis);
}
return DateTime.tryParse(date) ?? DateTime.now();
}
List<DateTime> generateMedicationDays(ActivePrescriptionsResponseModel med) {
final start = parseDate(med.startDate);
final duration = med.days ?? 0;
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;
}
else if (f.contains("once a week")) {
intervalDays = 7;
}
else if (f.contains("every 3 days")) {
intervalDays = 3;
}
else if (f.contains("every other day")) {
intervalDays = 2;
}
List<DateTime> result = [];
for (int offset = 0; offset < duration; offset += intervalDays) {
result.add(start.add(Duration(days: offset)));
}
return result;
}
bool sameYMD(DateTime a, DateTime b) =>
a.year == b.year && a.month == b.month && a.day == b.day;
List<ActivePrescriptionsResponseModel> 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, clean));
}).toList();
}
}

@ -0,0 +1,78 @@
class ActivePrescriptionsResponseModel {
String? itemId;
String? itemDescription;
String? route;
String? frequency;
int? frequencyNumber;
int? doseDailyQuantity;
int? days;
String? startDate;
String? endDate;
String? orderDate;
String? productImageString;
bool isReminderOn;
List<String?> selectedDoseTimes = [];
ActivePrescriptionsResponseModel({
this.itemId,
this.itemDescription,
this.route,
this.frequency,
this.frequencyNumber,
this.doseDailyQuantity,
this.days,
this.startDate,
this.endDate,
this.orderDate,
this.productImageString,
this.isReminderOn = false,
List<String?>? selectedDoseTimes,
}) {
this.selectedDoseTimes = selectedDoseTimes ?? [];
}
/// ========== JSON FROM ==========
factory ActivePrescriptionsResponseModel.fromJson(Map<String, dynamic> json) {
return ActivePrescriptionsResponseModel(
itemId: json["ItemID"]?.toString() ?? "",
itemDescription: json["ItemDescription"] ?? "",
route: json["Route"] ?? "",
frequency: json["Frequency"] ?? "",
frequencyNumber: json["FrequencyNumber"],
doseDailyQuantity: json["DoseDailyQuantity"] ?? 1,
days: json["Days"] ?? 0,
startDate: json["StartDate"] ?? "",
endDate: json["EndDate"] ?? "",
orderDate: json["OrderDate"] ?? "",
productImageString: json["ProductImageString"] ?? "",
isReminderOn: json["IsReminderOn"] == true,
selectedDoseTimes:
(json["SelectedDoseTimes"] as List<dynamic>?)
?.map((e) => e?.toString())
.toList() ??
[],
);
}
/// ========== JSON TO ==========
Map<String, dynamic> toJson() {
return {
"ItemID": itemId,
"ItemDescription": itemDescription,
"Route": route,
"Frequency": frequency,
"FrequencyNumber": frequencyNumber,
"DoseDailyQuantity": doseDailyQuantity,
"Days": days,
"StartDate": startDate,
"EndDate": endDate,
"OrderDate": orderDate,
"ProductImageString": productImageString,
"IsReminderOn": isReminderOn,
"SelectedDoseTimes": selectedDoseTimes,
};
}
}

@ -165,6 +165,27 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
// Sort filtered doctor list by clinic or hospital
void sortFilteredDoctorList(bool sortByClinic) {
isSortByClinic = sortByClinic;
if (sortByClinic) {
// Sort by clinic name
filteredDoctorList.sort((a, b) {
final clinicA = (a.clinicName ?? 'Unknown').toLowerCase();
final clinicB = (b.clinicName ?? 'Unknown').toLowerCase();
return clinicA.compareTo(clinicB);
});
} else {
// Sort by hospital/project name
filteredDoctorList.sort((a, b) {
final hospitalA = (a.projectName ?? 'Unknown').toLowerCase();
final hospitalB = (b.projectName ?? 'Unknown').toLowerCase();
return hospitalA.compareTo(hospitalB);
});
}
notifyListeners();
}
// Group doctors by clinic and hospital
void _groupDoctorsList() {
final clinicMap = <String, List<DoctorsListResponseModel>>{};

@ -8,8 +8,10 @@ class HmgServicesComponentModel {
Color? iconColor;
bool isLogin;
bool isLocked;
Color textColor;
Color bgColor;
String? route;
bool isExternalLink;
Function? onTap;
HmgServicesComponentModel(
@ -23,6 +25,8 @@ class HmgServicesComponentModel {
this.bgColor = Colors.white,
this.iconColor = Colors.white,
this.route,
this.onTap
this.onTap,
this.textColor = Colors.black,
this.isExternalLink = false,
});
}

@ -0,0 +1,96 @@
import 'package:dartz/dartz.dart';
import '../../core/api/api_client.dart';
import '../../core/api_consts.dart';
import '../../core/common_models/generic_api_model.dart';
import '../../core/exceptions/api_failure.dart';
import '../../services/logger_service.dart';
abstract class MonthlyReportsRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> saveMonthlyReport({
String? email,
});
}
class MonthlyReportsRepoImp implements MonthlyReportsRepo {
final ApiClient apiClient;
final LoggerService loggerService;
MonthlyReportsRepoImp({
required this.loggerService,
required this.apiClient,
});
@override
Future<Either<Failure, GenericApiModel<dynamic>>> saveMonthlyReport({
String? email,
}) async {
try {
Failure? failure;
GenericApiModel<dynamic>? reportApiResponse;
await apiClient.post(
ApiConsts.getMonthlyReports,
body: <String, dynamic>{},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
reportApiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (reportApiResponse == null) return Left(ServerFailure("Unknown error"));
if ((reportApiResponse!.messageStatus ?? 0) != 1) {
return Right(reportApiResponse!);
}
GenericApiModel<dynamic>? emailApiResponse;
final Map<String, dynamic> emailRequest = <String, dynamic>{};
if (email != null && email.trim().isNotEmpty) {
emailRequest["Email"] = email.trim();
}
await apiClient.post(
ApiConsts.updatePatientEmail,
body: emailRequest,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
emailApiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (emailApiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(emailApiResponse!);
} catch (e) {
loggerService.logError("MonthlyReportsRepo.saveMonthlyReport error: $e");
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'monthly_reports_repo.dart';
class MonthlyReportsViewModel extends ChangeNotifier {
final MonthlyReportsRepo monthlyReportsRepo;
final ErrorHandlerService errorHandlerService;
bool isLoading = false;
MonthlyReportsViewModel({
required this.monthlyReportsRepo,
required this.errorHandlerService,
});
Future<bool> saveMonthlyReport({String? email}) async {
isLoading = true;
notifyListeners();
final result = await monthlyReportsRepo.saveMonthlyReport(email: email);
final success = result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
return false;
},
(apiResponse) => (apiResponse.messageStatus ?? 0) == 1,
);
isLoading = false;
notifyListeners();
return success;
}
}

@ -0,0 +1,60 @@
import 'package:dartz/dartz.dart';
import '../../core/api/api_client.dart';
import '../../core/api_consts.dart';
import '../../core/exceptions/api_failure.dart';
import '../../services/logger_service.dart';
abstract class TermsConditionsRepo {
Future<Either<Failure, String>> getTermsConditions();
}
class TermsConditionsRepoImp implements TermsConditionsRepo {
final ApiClient apiClient;
final LoggerService loggerService;
TermsConditionsRepoImp({
required this.loggerService,
required this.apiClient,
});
@override
Future<Either<Failure, String>> getTermsConditions() async {
Failure? failure;
String? html;
try {
await apiClient.post(
ApiConsts.getTermsConditions,
body: <String, dynamic>{},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType ?? ServerFailure(error.toString());
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final content = response['UserAgreementContent'];
if (content is String && content.isNotEmpty) {
html = content;
} else {
failure = DataParsingFailure(
'UserAgreementContent is null or not String');
}
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
} catch (e) {
failure = UnknownFailure(e.toString());
}
if (failure != null) return Left(failure!);
if (html == null || html!.isEmpty) {
return Left(ServerFailure('No terms and conditions returned'));
}
return Right(html!);
}
}

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class TermsConditionsViewModel extends ChangeNotifier {
final TermsConditionsRepo termsConditionsRepo;
final ErrorHandlerService errorHandlerService;
String? termsConditionsHtml;
bool isLoading = false;
TermsConditionsViewModel({
required this.termsConditionsRepo,
required this.errorHandlerService,
});
Future<void> getTermsConditions({
Function()? onSuccess,
Function(String)? onError,
}) async {
isLoading = true;
notifyListeners();
final result = await termsConditionsRepo.getTermsConditions();
result.fold(
(failure) async {
await errorHandlerService.handleError(failure: failure);
isLoading = false;
notifyListeners();
if (onError != null) {
onError(failure.message ?? 'Something went wrong');
}
},
(html) {
termsConditionsHtml = html;
isLoading = false;
notifyListeners();
if (onSuccess != null) onSuccess();
},
);
}
}

@ -0,0 +1,183 @@
class QrParkingResponseModel {
dynamic totalRecords;
dynamic nRowID;
int? qRParkingID;
String? description;
String? descriptionN;
dynamic qRCompare;
dynamic qRValue;
String? imagePath;
bool? isActive;
int? parkingID;
int? branchID;
int? companyID;
int? buildingID;
int? rowID;
int? gateID;
int? floorID;
dynamic imagePath1;
int? createdBy;
String? createdOn;
dynamic editedBy;
dynamic editedOn;
String? parkingDescription;
String? parkingDescriptionN;
String? gateDescription;
String? gateDescriptionN;
String? branchDescription;
String? branchDescriptionN;
String? companyDescription;
String? companyDescriptionN;
String? rowDescription;
String? rowDescriptionN;
String? floorDescription;
String? floorDescriptionN;
String? buildingDescription;
String? buildingDescriptionN;
String? qRParkingCode;
String? parkingCode;
double? latitude;
double? longitude;
String? qRImageStr;
QrParkingResponseModel({
this.totalRecords,
this.nRowID,
this.qRParkingID,
this.description,
this.descriptionN,
this.qRCompare,
this.qRValue,
this.imagePath,
this.isActive,
this.parkingID,
this.branchID,
this.companyID,
this.buildingID,
this.rowID,
this.gateID,
this.floorID,
this.imagePath1,
this.createdBy,
this.createdOn,
this.editedBy,
this.editedOn,
this.parkingDescription,
this.parkingDescriptionN,
this.gateDescription,
this.gateDescriptionN,
this.branchDescription,
this.branchDescriptionN,
this.companyDescription,
this.companyDescriptionN,
this.rowDescription,
this.rowDescriptionN,
this.floorDescription,
this.floorDescriptionN,
this.buildingDescription,
this.buildingDescriptionN,
this.qRParkingCode,
this.parkingCode,
this.latitude,
this.longitude,
this.qRImageStr,
});
QrParkingResponseModel.fromJson(Map<String, dynamic> json) {
totalRecords = json['TotalRecords'];
nRowID = json['nRowID'];
qRParkingID = json['QRParkingID'];
description = json['Description'];
descriptionN = json['DescriptionN'];
qRCompare = json['QRCompare'];
qRValue = json['QRValue'];
imagePath = json['ImagePath'];
isActive = json['IsActive'];
parkingID = json['ParkingID'];
branchID = json['BranchID'];
companyID = json['CompanyID'];
buildingID = json['BuildingID'];
rowID = json['RowID'];
gateID = json['GateID'];
floorID = json['FloorID'];
imagePath1 = json['ImagePath1'];
createdBy = json['CreatedBy'];
createdOn = json['CreatedOn'];
editedBy = json['EditedBy'];
editedOn = json['EditedOn'];
parkingDescription = json['ParkingDescription'];
parkingDescriptionN = json['ParkingDescriptionN'];
gateDescription = json['GateDescription'];
gateDescriptionN = json['GateDescriptionN'];
branchDescription = json['BranchDescription'];
branchDescriptionN = json['BranchDescriptionN'];
companyDescription = json['CompanyDescription'];
companyDescriptionN = json['CompanyDescriptionN'];
rowDescription = json['RowDescription'];
rowDescriptionN = json['RowDescriptionN'];
floorDescription = json['FloorDescription'];
floorDescriptionN = json['FloorDescriptionN'];
buildingDescription = json['BuildingDescription'];
buildingDescriptionN = json['BuildingDescriptionN'];
qRParkingCode = json['QRParkingCode'];
parkingCode = json['ParkingCode'];
latitude = _toDouble(json['Latitude']);
longitude = _toDouble(json['Longitude']);
qRImageStr = json['QRImageStr'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['TotalRecords'] = totalRecords;
data['nRowID'] = nRowID;
data['QRParkingID'] = qRParkingID;
data['Description'] = description;
data['DescriptionN'] = descriptionN;
data['QRCompare'] = qRCompare;
data['QRValue'] = qRValue;
data['ImagePath'] = imagePath;
data['IsActive'] = isActive;
data['ParkingID'] = parkingID;
data['BranchID'] = branchID;
data['CompanyID'] = companyID;
data['BuildingID'] = buildingID;
data['RowID'] = rowID;
data['GateID'] = gateID;
data['FloorID'] = floorID;
data['ImagePath1'] = imagePath1;
data['CreatedBy'] = createdBy;
data['CreatedOn'] = createdOn;
data['EditedBy'] = editedBy;
data['EditedOn'] = editedOn;
data['ParkingDescription'] = parkingDescription;
data['ParkingDescriptionN'] = parkingDescriptionN;
data['GateDescription'] = gateDescription;
data['GateDescriptionN'] = gateDescriptionN;
data['BranchDescription'] = branchDescription;
data['BranchDescriptionN'] = branchDescriptionN;
data['CompanyDescription'] = companyDescription;
data['CompanyDescriptionN'] = companyDescriptionN;
data['RowDescription'] = rowDescription;
data['RowDescriptionN'] = rowDescriptionN;
data['FloorDescription'] = floorDescription;
data['FloorDescriptionN'] = floorDescriptionN;
data['BuildingDescription'] = buildingDescription;
data['BuildingDescriptionN'] = buildingDescriptionN;
data['QRParkingCode'] = qRParkingCode;
data['ParkingCode'] = parkingCode;
data['Latitude'] = latitude;
data['Longitude'] = longitude;
data['QRImageStr'] = qRImageStr;
return data;
}
static double? _toDouble(dynamic v) {
if (v == null) return null;
if (v is double) return v;
if (v is int) return v.toDouble();
return double.tryParse(v.toString());
}
}

@ -0,0 +1,74 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart';
import '../../core/api/api_client.dart';
import '../../core/api_consts.dart';
import '../../core/common_models/generic_api_model.dart';
import '../../core/exceptions/api_failure.dart';
import '../../services/logger_service.dart';
abstract class QrParkingRepo {
Future<Either<Failure, GenericApiModel<List<QrParkingResponseModel>>>>
getQrParking({
required int qrParkingId,
});
}
class QrParkingRepoImp implements QrParkingRepo {
final ApiClient apiClient;
final LoggerService loggerService;
QrParkingRepoImp({
required this.loggerService,
required this.apiClient,
});
@override
Future<Either<Failure, GenericApiModel<List<QrParkingResponseModel>>>>
getQrParking({required int qrParkingId}) async {
try {
GenericApiModel<List<QrParkingResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getQrParkingDetails, // GetQRParkingByID
body: {'QRParkingID': qrParkingId},
onFailure: (error, statusCode,
{messageStatus, failureType}) {
failure = failureType ??
StatusCodeFailure("$error ($statusCode)");
},
onSuccess: (response, statusCode,
{messageStatus, errorMessage}) {
final list =
(response['List_SWP_QRParkingModel'] as List?) ?? [];
final res = list
.map((e) => QrParkingResponseModel.fromJson(
Map<String, dynamic>.from(e),
))
.toList();
apiResponse = GenericApiModel<List<QrParkingResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: res,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) {
return Left(ServerFailure("Unknown error"));
}
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,144 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:barcode_scan2/barcode_scan2.dart';
import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart';
import '../../services/cache_service.dart';
import '../../services/error_handler_service.dart';
import 'models/qr_parking_response_model.dart';
class QrParkingViewModel extends ChangeNotifier {
final QrParkingRepo qrParkingRepo;
final ErrorHandlerService errorHandlerService;
final CacheService cacheService;
String IS_GO_TO_PARKING = 'IS_GO_TO_PARKING';
bool isLoading = false;
String? error;
bool isSavePark = false;
QrParkingResponseModel? qrParkingModel;
List<QrParkingResponseModel> qrParkingList = [];
QrParkingViewModel({
required this.qrParkingRepo,
required this.errorHandlerService,
required this.cacheService,
});
Future<QrParkingResponseModel?> scanAndGetParking() async {
try {
error = null;
isLoading = true;
notifyListeners();
final result = await BarcodeScanner.scan();
if (result.type != ResultType.Barcode) {
isLoading = false;
notifyListeners();
return null;
}
final raw = result.rawContent.trim();
if (raw.isEmpty) {
error = "Invalid QR Code";
isLoading = false;
notifyListeners();
return null;
}
final qrParkingId = _extractQrParkingId(raw);
if (qrParkingId == null) {
error = "Invalid QR Code";
isLoading = false;
notifyListeners();
return null;
}
final apiResult =
await qrParkingRepo.getQrParking(qrParkingId: qrParkingId);
final model = apiResult.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
error = failure.toString();
return null;
},
(apiResponse) {
qrParkingList = apiResponse.data ?? [];
if (qrParkingList.isNotEmpty) {
return qrParkingList.first;
}
error = "Invalid Qr Code";
return null;
},
);
if (model != null) {
qrParkingModel = model;
isSavePark = true;
await cacheService.saveObject(
key: IS_GO_TO_PARKING,
value: model.toJson(),
);
}
isLoading = false;
notifyListeners();
return model;
} catch (e) {
error = "Scan error";
isLoading = false;
notifyListeners();
return null;
}
}
/// Load saved parking
Future<void> getIsSaveParking() async {
isLoading = true;
notifyListeners();
final parking =
await cacheService.getObject(key: IS_GO_TO_PARKING);
if (parking != null) {
isSavePark = true;
qrParkingModel = QrParkingResponseModel.fromJson(
Map<String, dynamic>.from(parking),
);
} else {
isSavePark = false;
qrParkingModel = null;
}
isLoading = false;
notifyListeners();
}
/// Reset parking
Future<void> clearParking() async {
await cacheService.remove(key: IS_GO_TO_PARKING);
isSavePark = false;
qrParkingModel = null;
notifyListeners();
}
int? _extractQrParkingId(String raw) {
try {
if (raw.startsWith("{")) {
final data = jsonDecode(raw);
return int.tryParse(data['QRParkingID'].toString());
}
return int.tryParse(raw);
} catch (_) {
return null;
}
}
}

@ -475,7 +475,7 @@ abstract class LocaleKeys {
static const shareReview = 'shareReview';
static const review = 'review';
static const viewMedicalFile = 'viewMedicalFile';
static const viewAllServices = 'viewAllServices';
static String get viewAllServices => 'viewAllServices';
static const medicalFile = 'medicalFile';
static const verified = 'verified';
static const checkup = 'checkup';

@ -10,6 +10,7 @@ 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/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
@ -46,6 +47,7 @@ import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'core/utils/size_utils.dart';
import 'features/monthly_reports/terms_conditions_view_model.dart';
import 'firebase_options.dart';
@pragma('vm:entry-point')
@ -181,6 +183,12 @@ void main() async {
),
ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(),
),
ChangeNotifierProvider<ActivePrescriptionsViewModel>(
create: (_) => getIt.get<ActivePrescriptionsViewModel>(),
),
ChangeNotifierProvider<TermsConditionsViewModel>(
create: (_) => getIt.get<TermsConditionsViewModel>(),
)
], child: MyApp()),
),

@ -43,7 +43,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
body: Column(
children: [
Expanded(
child: CollapsingListView(
child: CollapsingListView(
title: "Choose Doctor".needTranslation,
child: SingleChildScrollView(
child: Padding(
@ -76,7 +76,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
)
: null,
onChange: (value) {
// bookAppointmentsViewModel.filterClinics(value!);
// bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
@ -89,43 +89,41 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
child: SizedBox(
height: 56.h,
width: 56.h,
child: DecoratedBox(decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 10.h,
hasShadow: false,
),
child: Utils.buildSvgWithAssets(icon: AppAssets.ic_filters,
height: 24.h,
width: 24.h, ).paddingAll(16.h).onPress((){
context.read<DoctorFilterViewModel>()
child: DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 10.h,
hasShadow: false,
),
child: Utils.buildSvgWithAssets(
icon: AppAssets.ic_filters,
height: 24.h,
width: 24.h,
).paddingAll(16.h).onPress(() {
context.read<DoctorFilterViewModel>()
..clearSelection()
..clearSearchFilters()
..getFiltersFromDoctorList(
bookAppointmentsViewModel.doctorsList
)..setSelections(
bookAppointmentsViewModel.selectedFacilityForFilters?.toList(),
bookAppointmentsViewModel.selectedRegionForFilters?.toList(),
bookAppointmentsViewModel.selectedClinicForFilters,
bookAppointmentsViewModel.selectedHospitalForFilters,
bookAppointmentsViewModel.applyFilters) ;
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0)
const end = Offset.zero; // End at the original position (y=0.0)
final tween = Tween(begin: begin, end: end);
final offsetAnimation = animation.drive(tween);
..clearSearchFilters()
..getFiltersFromDoctorList(bookAppointmentsViewModel.doctorsList)
..setSelections(bookAppointmentsViewModel.selectedFacilityForFilters?.toList(), bookAppointmentsViewModel.selectedRegionForFilters?.toList(),
bookAppointmentsViewModel.selectedClinicForFilters, bookAppointmentsViewModel.selectedHospitalForFilters, bookAppointmentsViewModel.applyFilters);
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0)
const end = Offset.zero; // End at the original position (y=0.0)
final tween = Tween(begin: begin, end: end);
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed
),
);
}),
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed
),
);
}),
),
),
)
@ -136,9 +134,43 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (bookAppointmentsVM.isDoctorSearchByNameStarted)
Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
bookAppointmentsVM.sortFilteredDoctorList(true);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
bookAppointmentsVM.sortFilteredDoctorList(false);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor,
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(0.h, 0.h),
bookAppointmentsVM.isDoctorSearchByNameStarted
? ListView.separated(
padding: EdgeInsets.only(top: 24.h),
padding: EdgeInsets.only(top: 20.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.filteredDoctorList.length,
@ -261,6 +293,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
),
);
}
@override
void dispose() {
bookAppointmentsViewModel.doctorsList.clear();

@ -25,6 +25,7 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -36,6 +37,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../../core/dependencies.dart' show getIt;
import '../../features/qr_parking/qr_parking_view_model.dart';
class ServicesPage extends StatelessWidget {
ServicesPage({super.key});
@ -527,7 +529,18 @@ class ServicesPage extends StatelessWidget {
SizedBox(width: 8.w),
"Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500)
],
),
).onPress(() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChangeNotifierProvider(
create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(),
),
),
);
}),
),
),
),

@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:url_launcher/url_launcher.dart';
class ServiceGridViewItem extends StatelessWidget {
final HmgServicesComponentModel hmgServiceComponentModel;
@ -16,18 +17,18 @@ class ServiceGridViewItem extends StatelessWidget {
final bool isHealthToolIcon;
final Function? onTap;
const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false, this.onTap, this.isHealthToolIcon = false});
const ServiceGridViewItem(
this.hmgServiceComponentModel, this.index, this.isHomePage,
{super.key, this.isLocked = false, required this.isHealthToolIcon, this.onTap});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
hmgServiceComponentModel.route != null
? getIt.get<NavigationService>().pushPageRoute(hmgServiceComponentModel.route!)
: hmgServiceComponentModel.onTap != null
? hmgServiceComponentModel.onTap!()
: null;
},
onTap: () => hmgServiceComponentModel.isExternalLink
? _openLink(hmgServiceComponentModel.route!)
: getIt
.get<NavigationService>()
.pushPageRoute(hmgServiceComponentModel.route!),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
@ -58,4 +59,14 @@ class ServiceGridViewItem extends StatelessWidget {
],
));
}
Future<void> _openLink(String link) async {
final Uri url = Uri.parse(link);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
} else {
throw "Could not launch $url";
}
}
}

@ -59,6 +59,8 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart';
import 'package:provider/provider.dart';
import '../active_medication/active_medication_page.dart';
class LandingPage extends StatefulWidget {
const LandingPage({super.key});

@ -16,6 +16,7 @@ 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';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
@ -32,6 +33,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/active_medication/active_medication_page.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
@ -75,6 +77,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart';
import 'package:provider/provider.dart';
import '../../features/active_prescriptions/active_prescriptions_view_model.dart';
import '../prescriptions/prescription_detail_page.dart';
import 'widgets/medical_file_appointment_card.dart';
@ -98,6 +101,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late MonthlyReportViewModel monthlyReportViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
late ActivePrescriptionsViewModel activePrescriptionsViewModel;
int currentIndex = 0;

@ -0,0 +1,310 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/monthly_reports/monthly_reports_view_model.dart';
import 'package:hmg_patient_app_new/presentation/monthly_reports/user_agreement_page.dart';
import 'package:provider/provider.dart';
import '../../generated/locale_keys.g.dart';
import '../../theme/colors.dart';
import '../../widgets/appbar/app_bar_widget.dart';
import '../../widgets/input_widget.dart';
import '../../widgets/loader/bottomsheet_loader.dart';
class MonthlyReportsPage extends StatefulWidget {
const MonthlyReportsPage({super.key});
@override
State<MonthlyReportsPage> createState() => _MonthlyReportsPageState();
}
class _MonthlyReportsPageState extends State<MonthlyReportsPage> {
bool isHealthSummaryEnabled = false;
bool isTermsAccepted = false;
final TextEditingController emailController = TextEditingController();
@override
void dispose() {
emailController.dispose();
super.dispose();
}
void _showError(String message) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
),
);
}
void _showSuccessSnackBar() {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Successfully updated".needTranslation,
style: const TextStyle(
color: AppColors.whiteColor,
fontWeight: FontWeight.w600,
),
),
behavior: SnackBarBehavior.floating,
backgroundColor: AppColors.textGreenColor,
duration: const Duration(seconds: 2),
),
);
}
Future<void> _onSavePressed() async {
if (!isTermsAccepted) {
_showError("Please accept the terms and conditions".needTranslation);
return;
}
final email = emailController.text.trim();
if (email.isEmpty) {
_showError("Please enter your email".needTranslation);
return;
}
final vm = context.read<MonthlyReportsViewModel>();
// LoaderBottomSheet.showLoader();
final ok = await vm.saveMonthlyReport(email: email);
// LoaderBottomSheet.hideLoader();
if (ok) {
setState(() => isHealthSummaryEnabled = true);
_showSuccessSnackBar();
} else {
// _showError("Failed to update".needTranslation);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
appBar: CustomAppBar(
onBackPressed: () => Navigator.of(context).pop(),
onLanguageChanged: (_) {},
hideLogoAndLang: true,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Monthly Reports".needTranslation,
style: TextStyle(
color: AppColors.textColor,
fontSize: 27.f,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
height: 54.h,
alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: (12.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Patient Health Summary Report".needTranslation,
style: TextStyle(
color: AppColors.textColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
),
),
_buildToggle(),
],
),
),
SizedBox(height: 16.h),
TextInputWidget(
controller: emailController,
labelText: "Eamil*".needTranslation,
hintText: "email@email.com",
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
autoFocus: true,
keyboardType: TextInputType.emailAddress,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
onChange: (value) {
setState(() {});
},
).paddingOnly(top: 8.h, bottom: 8.h),
Row(
children: [
Text(
"To View The Terms and Conditions".needTranslation,
style: TextStyle(
color: AppColors.textColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
),
),
InkWell(
child: Text(
"Click here".needTranslation,
style: TextStyle(
color: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const UserAgreementPage(),
),
);
},
),
],
),
SizedBox(height: 12.h),
GestureDetector(
onTap: () => setState(() => isTermsAccepted = !isTermsAccepted),
child: Row(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 24.h,
width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted
? AppColors.textGreenColor
: Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isTermsAccepted
? AppColors.lightGreenColor
: AppColors.greyColor,
width: 2.h,
),
),
child: isTermsAccepted
? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor,)
: null,
),
SizedBox(width: 12.h),
Text(
"I agree to the terms and conditions".needTranslation,
style: context.dynamicTextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
],
),
),
SizedBox(height: 12.h),
Text(
"This monthly Health Summary Report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as an official report so no medical decisions should be taken based on it"
.needTranslation,
style: TextStyle(
color: AppColors.textColor,
fontSize: 10.f,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 12.h),
Image.asset('assets/images/jpg/report.jpg'),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.successColor,
foregroundColor: AppColors.whiteColor,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: _onSavePressed,
child: Text(
LocaleKeys.save.tr(),
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.f,
),
),
),
),
],
),
],
),
).paddingAll(16),
);
}
Widget _buildToggle() {
final value = isHealthSummaryEnabled;
return AbsorbPointer(
absorbing: true,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 50.h,
height: 28.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: value
? AppColors.lightGreenColor
: AppColors.greyColor.withOpacity(0.3),
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
alignment: value ? Alignment.centerRight : Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(3),
child: Container(
width: 22.h,
height: 22.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: value
? AppColors.textGreenColor
: AppColors.greyTextColor,
),
),
),
),
),
);
}
}

@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_view_model.dart';
import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../theme/colors.dart';
import '../../widgets/appbar/app_bar_widget.dart';
class UserAgreementPage extends StatefulWidget {
const UserAgreementPage({super.key});
@override
State<UserAgreementPage> createState() => _UserAgreementPageState();
}
class _UserAgreementPageState extends State<UserAgreementPage> {
late final WebViewController _webViewController;
bool _isLoading = true;
String? _errorMessage;
@override
void initState() {
super.initState();
_webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (_) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (_) {
setState(() {
_isLoading = false;
});
},
onWebResourceError: (error) {
},
),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
final vm =
Provider.of<TermsConditionsViewModel>(context, listen: false);
vm.getTermsConditions(
onSuccess: () {
final htmlString = vm.termsConditionsHtml ?? '';
if (htmlString.isNotEmpty) {
setState(() {
_errorMessage = null;
_isLoading = true;
});
_webViewController.loadHtmlString(htmlString);
} else {
setState(() {
_isLoading = false;
_errorMessage = 'لا توجد شروط متاحة حالياً'.needTranslation;
});
}
},
onError: (msg) {
setState(() {
_isLoading = false;
_errorMessage = msg;
});
},
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
appBar: CustomAppBar(
onBackPressed: () => Navigator.of(context).pop(),
onLanguageChanged: (_) {},
hideLogoAndLang: true,
),
body: Stack(
children: [
WebViewWidget(controller: _webViewController),
if (_errorMessage != null)
Center(
child: Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_errorMessage!,
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.w600,
),
),
),
),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
);
}
}

@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/parking/parking_slot.dart';
import 'package:provider/provider.dart';
import '../../features/qr_parking/qr_parking_view_model.dart';
import '../../theme/colors.dart';
import '../../widgets/appbar/app_bar_widget.dart';
import '../../widgets/routes/custom_page_route.dart';
class ParkingPage extends StatefulWidget {
const ParkingPage({super.key});
@override
State<ParkingPage> createState() => _ParkingPageState();
}
class _ParkingPageState extends State<ParkingPage> {
Future<void> _readQR(BuildContext context) async {
final vm = context.read<QrParkingViewModel>();
final model = await vm.scanAndGetParking();
if (model == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(vm.error ?? "Invalid Qr Code")),
);
return;
}
Navigator.of(context).push(
CustomPageRoute(
page: ParkingSlot(model: model),
),
);
}
@override
Widget build(BuildContext context) {
final vm = context.watch<QrParkingViewModel>(); // عشان loading
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
appBar: CustomAppBar(
onBackPressed: () => Navigator.of(context).pop(),
onLanguageChanged: (_) {},
hideLogoAndLang: true,
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Parking".needTranslation,
style: TextStyle(
color: AppColors.textColor,
fontSize: 27.f,
fontWeight: FontWeight.w600,
),
),
Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Text(
"Dr. Sulaiman Al Habib hospital are conduction a test for the emerging corona"
" virus and issuing travel certificates 24/7 in a short time and with high accuracy."
" Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches "
"to conduct a corona test within few minutes. Dr. Sulaiman Al Habib hospital are conduction"
" a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. "
"Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes.",
style: TextStyle(
color: AppColors.textColor,
fontSize: 12,
height: 1.4,
fontWeight: FontWeight.w500,
),
),
),
).paddingOnly(top: 16, bottom: 16),
],
),
),
),
/// Bottom button
Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(24.h),
child: SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryRedColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: vm.isLoading ? null : () => _readQR(context),
child: vm.isLoading
? const SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
"Read Barcodes",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
),
],
),
);
}
}

@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart';
import '../../features/qr_parking/qr_parking_view_model.dart';
import '../../theme/colors.dart';
import '../../widgets/appbar/app_bar_widget.dart';
import '../../widgets/chip/app_custom_chip_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:provider/provider.dart';
class ParkingSlot extends StatefulWidget {
final QrParkingResponseModel model;
const ParkingSlot({
super.key,
required this.model,
});
@override
State<ParkingSlot> createState() => _ParkingSlotState();
}
class _ParkingSlotState extends State<ParkingSlot> {
void _openDirection() {
final lat = widget.model.latitude;
final lng = widget.model.longitude;
final valid = lat != null &&
lng != null &&
!(lat == 0.0 && lng == 0.0) &&
lat >= -90 && lat <= 90 &&
lng >= -180 && lng <= 180;
if (!valid) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Parking location not available")),
);
return;
}
MapsLauncher.launchCoordinates(lat, lng);
}
Future<void> _resetDirection() async {
final vm = context.read<QrParkingViewModel>();
await vm.clearParking();
Navigator.of(context).popUntil((route) => route.isFirst);
}
DateTime? _parseDotNetDate(String? value) {
if (value == null || value.isEmpty) return null;
final regExp = RegExp(r'Date\((\d+)([+-]\d+)?\)');
final match = regExp.firstMatch(value);
if (match == null) return null;
final milliseconds = int.tryParse(match.group(1)!);
if (milliseconds == null) return null;
return DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true)
.toLocal();
}
String _formatPrettyDate(String? value) {
final date = _parseDotNetDate(value);
if (date == null) return '-';
const months = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
final day = date.day;
final month = months[date.month - 1];
final year = date.year;
return "$day $month $year";
}
String _formatPrettyTime(String? value) {
final date = _parseDotNetDate(value);
if (date == null) return '-';
int hour = date.hour;
final minute = date.minute.toString().padLeft(2, '0');
final isPM = hour >= 12;
final period = isPM ? 'PM' : 'AM';
hour = hour % 12;
if (hour == 0) hour = 12;
return "${hour.toString().padLeft(2, '0')}:$minute $period";
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
appBar: CustomAppBar(
onBackPressed: () => Navigator.of(context).pop(),
onLanguageChanged: (_) {},
hideLogoAndLang: true,
),
body: LayoutBuilder(
builder: (context, constraints) {
final maxW = constraints.maxWidth;
final contentW = maxW > 600 ? 600.0 : maxW;
return Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: contentW,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: double.infinity,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Parking Slot Details".needTranslation,
style: TextStyle(
fontSize: 16.f,
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
SizedBox(height: 16.h),
Wrap(
spacing: 4,
runSpacing: 4,
children: [
AppCustomChipWidget(
labelText:
"Slot: ${widget.model.qRParkingCode ?? '-'}"
.needTranslation,
),
AppCustomChipWidget(
labelText:
"Basement: ${widget.model.floorDescription ?? '-'}"
.needTranslation,
),
AppCustomChipWidget(
labelText:
"Date: ${_formatPrettyDate(widget.model.createdOn)}"
.needTranslation,
),
AppCustomChipWidget(
labelText:
"Parked Since: ${_formatPrettyTime(widget.model.createdOn)}"
.needTranslation,
),
],
),
],
),
),
),
SizedBox(height: 24.h),
SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryRedColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: _openDirection,
child: Text(
"Get Direction".needTranslation,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.whiteColor,
),
),
),
),
// const Spacer(),
// SizedBox(
// width: double.infinity,
// height: 48.h,
// child: OutlinedButton(
// style: OutlinedButton.styleFrom(
// side: BorderSide(color: AppColors.primaryRedColor),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(10),
// ),
// ),
// onPressed: _resetDirection,
// child: Text(
// "Reset Direction".needTranslation,
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.w600,
// color: AppColors.primaryRedColor,
// ),
// ),
// ),
// ),
],
),
),
),
);
},
),
);
}
}

@ -216,6 +216,14 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
}
Widget _historyCard(BuildContext context, {required List<DataPoint> history}) {
// For blood pressure, we need both systolic and diastolic series
List<DataPoint>? secondaryHistory;
if (args.metric == VitalSignMetric.bloodPressure) {
secondaryHistory = _buildBloodPressureDiastolicSeries(
context.read<HmgServicesViewModel>().vitalSignList,
);
}
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
@ -282,7 +290,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
if (history.isEmpty)
Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true)
else if (_isGraphVisible)
_buildHistoryGraph(history)
_buildHistoryGraph(history, secondaryHistory: secondaryHistory)
else
_buildHistoryList(context, history),
],
@ -290,31 +298,25 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
);
}
Widget _buildHistoryGraph(List<DataPoint> history) {
final minY = _minY(history);
final maxY = _maxY(history);
Widget _buildHistoryGraph(List<DataPoint> history, {List<DataPoint>? secondaryHistory}) {
final minY = _minY(history, secondaryHistory: secondaryHistory);
final maxY = _maxY(history, secondaryHistory: secondaryHistory);
final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title);
return CustomGraph(
dataPoints: history,
secondaryDataPoints: secondaryHistory,
makeGraphBasedOnActualValue: true,
leftLabelReservedSize: 40,
showGridLines: true,
showShadow: true,
leftLabelInterval: _leftInterval(history),
leftLabelInterval: _leftInterval(history, secondaryHistory: secondaryHistory),
maxY: maxY,
minY: minY,
maxX: history.length.toDouble() - .75,
horizontalInterval: _leftInterval(history),
horizontalInterval: _leftInterval(history, secondaryHistory: secondaryHistory),
leftLabelFormatter: (value) {
// Show labels at interval points
if (args.high != null && (value - args.high!).abs() < 0.1) {
return _axisLabel('High');
}
if (args.low != null && (value - args.low!).abs() < 0.1) {
return _axisLabel('Low');
}
// Show numeric labels at regular intervals
// Show only numeric labels at regular intervals
return _axisLabel(value.toStringAsFixed(0));
},
getDrawingHorizontalLine: (value) {
@ -341,6 +343,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
);
},
graphColor: AppColors.bgGreenColor,
secondaryGraphColor: AppColors.blueColor,
graphShadowColor: AppColors.lightGreenColor.withOpacity(.4),
graphGridColor: scheme.iconFg,
bottomLabelFormatter: (value, data) {
@ -350,7 +353,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label);
return const SizedBox.shrink();
},
rangeAnnotations: _rangeAnnotations(history),
rangeAnnotations: _rangeAnnotations(history, secondaryHistory: secondaryHistory),
minX: (history.length == 1) ? null : -.2,
scrollDirection: Axis.horizontal,
height: 180.h,
@ -361,6 +364,15 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
Widget _buildHistoryList(BuildContext context, List<DataPoint> history) {
final items = history.reversed.toList();
final height = items.length < 3 ? items.length * 64.0 : 180.h;
// Get diastolic values if this is blood pressure
List<DataPoint>? secondaryItems;
if (args.metric == VitalSignMetric.bloodPressure) {
final viewModel = context.read<HmgServicesViewModel>();
final secondaryHistory = _buildBloodPressureDiastolicSeries(viewModel.vitalSignList);
secondaryItems = secondaryHistory.reversed.toList();
}
return SizedBox(
height: height,
child: ListView.separated(
@ -372,13 +384,25 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
),
itemBuilder: (context, index) {
final dp = items[index];
// Build the value text based on metric type
String valueText;
if (args.metric == VitalSignMetric.bloodPressure && secondaryItems != null && index < secondaryItems.length) {
// Show systolic/diastolic for blood pressure
final diastolic = secondaryItems[index];
valueText = '${dp.actualValue}/${diastolic.actualValue} ${dp.unitOfMeasurement ?? ''}';
} else {
// Show single value for other metrics
valueText = '${dp.actualValue} ${dp.unitOfMeasurement ?? ''}';
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
dp.displayTime.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500),
('${dp.actualValue} ${dp.unitOfMeasurement ?? ''}').toText12(
valueText.toText12(
color: AppColors.textColor,
fontWeight: FontWeight.w600,
),
@ -390,34 +414,46 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
);
}
double _minY(List<DataPoint> points) {
double _minY(List<DataPoint> points, {List<DataPoint>? secondaryHistory}) {
// IMPORTANT: y-axis uses actual numeric values (from actualValue).
final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList();
// Include secondary data points if provided (for blood pressure)
if (secondaryHistory != null && secondaryHistory.isNotEmpty) {
values.addAll(secondaryHistory.map((e) => double.tryParse(e.actualValue) ?? 0));
}
final min = values.reduce((a, b) => a < b ? a : b);
final double boundLow = args.low ?? min;
return (min < boundLow ? min : boundLow) - 1;
}
double _maxY(List<DataPoint> points) {
double _maxY(List<DataPoint> points, {List<DataPoint>? secondaryHistory}) {
// IMPORTANT: y-axis uses actual numeric values (from actualValue).
final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList();
// Include secondary data points if provided (for blood pressure)
if (secondaryHistory != null && secondaryHistory.isNotEmpty) {
values.addAll(secondaryHistory.map((e) => double.tryParse(e.actualValue) ?? 0));
}
final max = values.reduce((a, b) => a > b ? a : b);
final double boundHigh = args.high ?? max;
return (max > boundHigh ? max : boundHigh) + 1;
}
double _leftInterval(List<DataPoint> points) {
double _leftInterval(List<DataPoint> points, {List<DataPoint>? secondaryHistory}) {
// Keep it stable; graph will mostly show just two labels.
final range = (_maxY(points) - _minY(points)).abs();
final range = (_maxY(points, secondaryHistory: secondaryHistory) - _minY(points, secondaryHistory: secondaryHistory)).abs();
if (range <= 0) return 1;
return (range / 4).clamp(1, 20);
}
RangeAnnotations? _rangeAnnotations(List<DataPoint> points) {
RangeAnnotations? _rangeAnnotations(List<DataPoint> points, {List<DataPoint>? secondaryHistory}) {
if (args.low == null && args.high == null) return null;
final minY = _minY(points);
final maxY = _maxY(points);
final minY = _minY(points, secondaryHistory: secondaryHistory);
final maxY = _maxY(points, secondaryHistory: secondaryHistory);
final List<HorizontalRangeAnnotation> ranges = [];
@ -480,7 +516,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
case VitalSignMetric.respiratoryRate:
return _toDouble(v.respirationBeatPerMinute);
case VitalSignMetric.bloodPressure:
// Graph only systolic for now (simple single-series).
// Graph systolic for primary series
return _toDouble(v.bloodPressureHigher);
}
}
@ -513,6 +549,46 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
return points;
}
/// Build diastolic blood pressure series for dual-line graph
List<DataPoint> _buildBloodPressureDiastolicSeries(List<VitalSignResModel> vitals) {
final List<DataPoint> points = [];
// Build a chronological series (oldest -> newest), skipping null/zero values.
final sorted = List<VitalSignResModel>.from(vitals);
sorted.sort((a, b) {
final ad = a.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0);
final bd = b.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0);
return ad.compareTo(bd);
});
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
double index = 0;
for (final v in sorted) {
final diastolic = _toDouble(v.bloodPressureLower);
if (diastolic == null) continue;
if (diastolic == 0) continue;
final dt = v.vitalSignDate ?? DateTime.now();
final label = '${monthNames[dt.month - 1]}, ${dt.year}';
points.add(
DataPoint(
value: index,
label: label,
actualValue: diastolic.toStringAsFixed(0),
time: dt,
displayTime: '${dt.day}/${dt.month}/${dt.year}',
unitOfMeasurement: args.unit,
),
);
index += 1;
}
return points;
}
double? _toDouble(dynamic v) {
if (v == null) return null;
if (v is num) return v.toDouble();

@ -31,6 +31,17 @@ import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
import '../features/qr_parking/qr_parking_view_model.dart';
import '../presentation/covid19test/covid19_landing_page.dart';
import '../core/dependencies.dart';
import '../features/monthly_reports/monthly_reports_repo.dart';
import '../features/monthly_reports/monthly_reports_view_model.dart';
import '../presentation/monthly_reports/monthly_reports_page.dart';
import '../presentation/parking/paking_page.dart';
import '../services/error_handler_service.dart';
import 'package:provider/provider.dart';
class AppRoutes {
static const String initialRoute = '/initialRoute';
static const String loginScreen = '/loginScreen';
@ -66,6 +77,8 @@ class AppRoutes {
static const String triagePage = '/triageProgressScreen';
static const String userInfoSelection = '/userInfoSelection';
static const String userInfoFlowManager = '/userInfoFlowManager';
static const String monthlyReports = '/monthlyReportsPage';
static const String qrParking = '/qrParkingPage';
// Health Trackers
static const String healthTrackersPage = '/healthTrackersListScreen';
@ -113,6 +126,17 @@ class AppRoutes {
return HealthTrackerDetailPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
monthlyReports: (context) => ChangeNotifierProvider(
create: (_) => MonthlyReportsViewModel(
monthlyReportsRepo: getIt<MonthlyReportsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
),
child: const MonthlyReportsPage(),
),
qrParking: (context) => ChangeNotifierProvider<QrParkingViewModel>(
create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(),
},
};
}

@ -14,6 +14,8 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/family_files/family_file_add_widget.dart';
import '../widgets/medication_reminder/reminder_timer_dialog.dart';
abstract class DialogService {
Future<void> showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed});
@ -33,6 +35,8 @@ abstract class DialogService {
Future<void> showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress});
Future<void> showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress});
Future<void> showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed});
// TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here
}
@ -160,6 +164,18 @@ class DialogServiceImp implements DialogService {
);
}
@override
Future<void> showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
context,
title: label ?? "",
child: child ?? SizedBox(),
callBackFunc: () {},
);
}
@override
Future<void> showPhoneNumberPickerSheet(
{String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {

@ -26,6 +26,7 @@ import 'package:lottie/lottie.dart';
import 'core/cache_consts.dart';
import 'core/utils/push_notification_handler.dart';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});

@ -110,4 +110,6 @@ class AppColors {
static const Color shimmerBaseColor = Color(0xFFE0E0E0); // #E0E0E0
static const Color shimmerHighlightColor = Color(0xFFF5F5F5); // #F5F5F5
static const Color covid29Color = Color(0xff2563EB); // #2563EB
static const Color lightGreyTextColor = Color(0xFF959595);
static const Color labelColorYellow = Color(0xFFFBCB6E);
}

@ -130,9 +130,23 @@ class CustomGraph extends StatelessWidget {
minX: minX,
lineTouchData: LineTouchData(
getTouchLineEnd: (_, __) => 0,
handleBuiltInTouches: true,
touchCallback: (FlTouchEvent event, LineTouchResponse? touchResponse) {
// Let fl_chart handle the touch
},
getTouchedSpotIndicator: (barData, indicators) {
// Only show custom marker for touched spot
// Show custom marker for touched spot with correct color per line
return indicators.map((int index) {
// Determine which line is being touched based on barData
Color dotColor = spotColor;
if (secondaryDataPoints != null && barData.spots.length > 0) {
// Check if this is the secondary line by comparing the first spot's color
final gradient = barData.gradient;
if (gradient != null && gradient.colors.isNotEmpty) {
dotColor = gradient.colors.first;
}
}
return TouchedSpotIndicatorData(
FlLine(color: Colors.transparent),
FlDotData(
@ -140,7 +154,7 @@ class CustomGraph extends StatelessWidget {
getDotPainter: (spot, percent, barData, idx) {
return FlDotCirclePainter(
radius: 8,
color: spotColor,
color: dotColor,
strokeWidth: 2,
strokeColor: Colors.white,
);
@ -154,17 +168,18 @@ class CustomGraph extends StatelessWidget {
getTooltipColor: (_) => Colors.white,
getTooltipItems: (touchedSpots) {
if (touchedSpots.isEmpty) return [];
// Only show tooltip for the first touched spot, hide others
// Show tooltip for each touched line
return touchedSpots.map((spot) {
if (spot == touchedSpots.first) {
final dataPoint = dataPoints[spot.x.toInt()];
// Determine which dataset this spot belongs to
final isSecondary = secondaryDataPoints != null && spot.barIndex == 1;
final dataPoint = isSecondary
? secondaryDataPoints![spot.x.toInt()]
: dataPoints[spot.x.toInt()];
return LineTooltipItem(
'${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}',
TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500),
);
}
return null; // hides the rest
return LineTooltipItem(
'${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}',
TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500),
);
}).toList();
},
),

@ -0,0 +1,155 @@
// import 'package:easy_localization/easy_localization.dart';
// import 'package:flutter/material.dart';
// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
//
// import '../../generated/locale_keys.g.dart';
// import '../../theme/colors.dart';
//
// class ReminderTimerDialog extends StatefulWidget {
// final Function()? onSetReminderPress;
// final String message;
//
// const ReminderTimerDialog(this.onSetReminderPress, this.message, {super.key});
//
//
// @override
// State<ReminderTimerDialog> createState() => _ReminderTimerDialogState();
// }
//
// class _ReminderTimerDialogState extends State<ReminderTimerDialog> {
// final List<String> options = ["Morning", "Afternoon", "Evening", "Midnight"];
// final List<String> selectedTimes = ["Morning"]; // Default selection
//
//
// @override
// Widget build(BuildContext context) {
// return //
// Column(
// children: [
// Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24,
// hasShadow: true,),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// // Checkboxes list
// children: options.map((time) => buildCircleCheckbox(time)).toList(),
// ).paddingAll(16),
// ),
// const SizedBox(height: 25),
// // Buttons Row
// Row(
// children: [
// Expanded(
// child: ElevatedButton.icon(
// onPressed: () => Navigator.pop(context),
// icon: const Icon(Icons.close, color: AppColors.errorColor),
// label: Text(
// LocaleKeys.cancel.tr(),
// style: TextStyle(
// color: AppColors.errorColor,
// fontWeight: FontWeight.w500,
// fontSize: 14
// ),
// ),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppColors.secondaryLightRedColor,
// elevation: 0,
// padding: const EdgeInsets.symmetric(vertical: 14),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12),
// ),
// ),
// ),
// ),
// const SizedBox(width: 12),
// Expanded(
// child: ElevatedButton.icon(
// onPressed: () {
// Navigator.pop(context, selectedTimes);
// },
// icon: const Icon(Icons.notifications_rounded),
// label: Text(
// LocaleKeys.setReminder.tr(),
// style: TextStyle(
// fontWeight: FontWeight.w500,
// fontSize: 14
// ),
// ),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppColors.successColor,
// foregroundColor: AppColors.whiteColor,
// elevation: 0,
// padding: const EdgeInsets.symmetric(vertical: 14),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12),
// ),
// ),
// ),
// ),
// ],
// ),
// const SizedBox(height: 30),
// ],
// );
// }
//
// Widget buildCircleCheckbox(String label) {
// final bool isSelected = selectedTimes.contains(label);
// return InkWell(
// onTap: () {
// setState(() {
// if (isSelected) {
// selectedTimes.remove(label);
// } else {
// selectedTimes.add(label);
// }
// });
// },
// borderRadius: BorderRadius.circular(25),
// child: Padding(
// padding: const EdgeInsets.symmetric(vertical: 8.0),
// child: Row(
// children: [
// // Custom circle checkbox
// Container(
// width: 15,
// height: 15,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(
// color: isSelected ? AppColors.spacerLineColor: AppColors.spacerLineColor,
// width: 1,
// ),
// color: isSelected ? AppColors.errorColor: AppColors.transparent,
// ),
// ),
// const SizedBox(width: 12),
// // Label text
// Text(
// label,
// style: const TextStyle(fontSize: 16, color: Colors.black87),
// ),
// ],
// ),
// ),
// );
// }
//
//
// void showCircleCheckboxDialog(BuildContext context) async {
// final selected = await showDialog<List<String>>(
// context: context,
// builder: (context) => const ReminderTimerDialog(),
// );
//
// if (selected != null && selected.isNotEmpty) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Reminders set for: ${selected.join(', ')}')),
// );
// }
// }
// }
//
//
//
Loading…
Cancel
Save