fatima #152

Merged
Haroon6138 merged 24 commits from fatima into master 3 days 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_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate";
var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate"; 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 //URL to get clinic list
var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized"; 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 getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus';
static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile';
static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; 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 // Ancillary Order Apis
static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; 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 ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.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/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_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.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/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_repo.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.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_repo.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.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/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_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.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'; 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/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/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/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/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@ -68,8 +72,13 @@ import 'package:local_auth/local_auth.dart';
import 'package:logger/web.dart'; import 'package:logger/web.dart';
import 'package:shared_preferences/shared_preferences.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 '../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; GetIt getIt = GetIt.instance;
class AppDependencies { class AppDependencies {
@ -147,6 +156,19 @@ class AppDependencies {
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(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<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 // ViewModels
// Global/shared VMs LazySingleton // Global/shared VMs LazySingleton
@ -274,5 +296,19 @@ class AppDependencies {
navServices: getIt(), navServices: getIt(),
)); ));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: 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:device_calendar_plus/device_calendar_plus.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:jiffy/jiffy.dart' show Jiffy; import 'package:jiffy/jiffy.dart' show Jiffy;
import 'package:manage_calendar_events/manage_calendar_events.dart' hide Calendar;
class CalenderUtilsNew { class CalenderUtilsNew {
final DeviceCalendar calender = DeviceCalendar.instance; 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,
};
}
}

@ -8,8 +8,10 @@ class HmgServicesComponentModel {
Color? iconColor; Color? iconColor;
bool isLogin; bool isLogin;
bool isLocked; bool isLocked;
Color textColor;
Color bgColor; Color bgColor;
String? route; String? route;
bool isExternalLink;
Function? onTap; Function? onTap;
HmgServicesComponentModel( HmgServicesComponentModel(
@ -23,6 +25,8 @@ class HmgServicesComponentModel {
this.bgColor = Colors.white, this.bgColor = Colors.white,
this.iconColor = Colors.white, this.iconColor = Colors.white,
this.route, 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 shareReview = 'shareReview';
static const review = 'review'; static const review = 'review';
static const viewMedicalFile = 'viewMedicalFile'; static const viewMedicalFile = 'viewMedicalFile';
static const viewAllServices = 'viewAllServices'; static String get viewAllServices => 'viewAllServices';
static const medicalFile = 'medicalFile'; static const medicalFile = 'medicalFile';
static const verified = 'verified'; static const verified = 'verified';
static const checkup = 'checkup'; 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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.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/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/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/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_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 'package:provider/single_child_widget.dart';
import 'core/utils/size_utils.dart'; import 'core/utils/size_utils.dart';
import 'features/monthly_reports/terms_conditions_view_model.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
@pragma('vm:entry-point') @pragma('vm:entry-point')
@ -181,6 +183,12 @@ void main() async {
), ),
ChangeNotifierProvider<MonthlyReportViewModel>( ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(), create: (_) => getIt.get<MonthlyReportViewModel>(),
),
ChangeNotifierProvider<ActivePrescriptionsViewModel>(
create: (_) => getIt.get<ActivePrescriptionsViewModel>(),
),
ChangeNotifierProvider<TermsConditionsViewModel>(
create: (_) => getIt.get<TermsConditionsViewModel>(),
) )
], child: MyApp()), ], child: MyApp()),
), ),

@ -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/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/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/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/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/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 'package:provider/provider.dart';
import '../../core/dependencies.dart' show getIt; import '../../core/dependencies.dart' show getIt;
import '../../features/qr_parking/qr_parking_view_model.dart';
class ServicesPage extends StatelessWidget { class ServicesPage extends StatelessWidget {
ServicesPage({super.key}); ServicesPage({super.key});
@ -527,7 +529,18 @@ class ServicesPage extends StatelessWidget {
SizedBox(width: 8.w), SizedBox(width: 8.w),
"Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) "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/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/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:url_launcher/url_launcher.dart';
class ServiceGridViewItem extends StatelessWidget { class ServiceGridViewItem extends StatelessWidget {
final HmgServicesComponentModel hmgServiceComponentModel; final HmgServicesComponentModel hmgServiceComponentModel;
@ -16,18 +17,18 @@ class ServiceGridViewItem extends StatelessWidget {
final bool isHealthToolIcon; final bool isHealthToolIcon;
final Function? onTap; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
onTap: () { onTap: () => hmgServiceComponentModel.isExternalLink
hmgServiceComponentModel.route != null ? _openLink(hmgServiceComponentModel.route!)
? getIt.get<NavigationService>().pushPageRoute(hmgServiceComponentModel.route!) : getIt
: hmgServiceComponentModel.onTap != null .get<NavigationService>()
? hmgServiceComponentModel.onTap!() .pushPageRoute(hmgServiceComponentModel.route!),
: null;
},
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start, 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:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../active_medication/active_medication_page.dart';
class LandingPage extends StatefulWidget { class LandingPage extends StatefulWidget {
const LandingPage({super.key}); 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/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_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/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/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/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_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/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/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.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/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_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_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:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../features/active_prescriptions/active_prescriptions_view_model.dart';
import '../prescriptions/prescription_detail_page.dart'; import '../prescriptions/prescription_detail_page.dart';
import 'widgets/medical_file_appointment_card.dart'; import 'widgets/medical_file_appointment_card.dart';
@ -98,6 +101,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late MonthlyReportViewModel monthlyReportViewModel; late MonthlyReportViewModel monthlyReportViewModel;
final CacheService cacheService = GetIt.instance<CacheService>(); final CacheService cacheService = GetIt.instance<CacheService>();
late ActivePrescriptionsViewModel activePrescriptionsViewModel;
int currentIndex = 0; 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,
// ),
// ),
// ),
// ),
],
),
),
),
);
},
),
);
}
}

@ -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/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/splashPage.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 { class AppRoutes {
static const String initialRoute = '/initialRoute'; static const String initialRoute = '/initialRoute';
static const String loginScreen = '/loginScreen'; static const String loginScreen = '/loginScreen';
@ -66,6 +77,8 @@ class AppRoutes {
static const String triagePage = '/triageProgressScreen'; static const String triagePage = '/triageProgressScreen';
static const String userInfoSelection = '/userInfoSelection'; static const String userInfoSelection = '/userInfoSelection';
static const String userInfoFlowManager = '/userInfoFlowManager'; static const String userInfoFlowManager = '/userInfoFlowManager';
static const String monthlyReports = '/monthlyReportsPage';
static const String qrParking = '/qrParkingPage';
// Health Trackers // Health Trackers
static const String healthTrackersPage = '/healthTrackersListScreen'; static const String healthTrackersPage = '/healthTrackersListScreen';
@ -113,6 +126,17 @@ class AppRoutes {
return HealthTrackerDetailPage( return HealthTrackerDetailPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, 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/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/family_files/family_file_add_widget.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 { abstract class DialogService {
Future<void> showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed}); 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> showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress});
Future<void> showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}); 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 // 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 @override
Future<void> showPhoneNumberPickerSheet( Future<void> showPhoneNumberPickerSheet(
{String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { {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/cache_consts.dart';
import 'core/utils/push_notification_handler.dart'; import 'core/utils/push_notification_handler.dart';
class SplashPage extends StatefulWidget { class SplashPage extends StatefulWidget {
const SplashPage({super.key}); const SplashPage({super.key});

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

@ -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