book appointment implementation completed.

pull/34/head
haroon amjad 2 months ago
parent 2aa84e0334
commit 9f95150a94

File diff suppressed because one or more lines are too long

@ -144,4 +144,5 @@ class AppAnimations {
static const String checkmark = '$lottieBasePath/checkmark.json';
static const String loadingAnimation = '$lottieBasePath/Loader.json';
static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json';
static const String warningAnimation = '$lottieBasePath/warningAnimation.json';
}

@ -145,7 +145,6 @@ class AppDependencies {
),
);
getIt.registerLazySingleton<MedicalFileViewModel>(
() => MedicalFileViewModel(
medicalFileRepo: getIt(),
@ -157,6 +156,8 @@ class AppDependencies {
() => BookAppointmentsViewModel(
bookAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt()
),
);

@ -2,6 +2,9 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -12,7 +15,7 @@ class LoadingUtils {
static bool get isLoading => _isLoadingVisible;
static showFullScreenLoader({bool barrierDismissible = true}) {
static showFullScreenLoader({bool barrierDismissible = true, isSuccessDialog = false, String loadingText = "Loading, Please wait..."}) {
if (!_isLoadingVisible) {
_isLoadingVisible = true;
final context = _navigationService.navigatorKey.currentContext;
@ -24,11 +27,21 @@ class LoadingUtils {
context: context,
barrierColor: AppColors.blackColor.withOpacity(0.5),
useRootNavigator: false,
builder: (BuildContext context) => Center(
child: CircularProgressIndicator(
color: AppColors.primaryRedColor,
)),
).then((value) {
useSafeArea: false,
builder: (BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Material(
child: Center(
child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText),
),
),
);
}).then((value) {
_isLoadingVisible = false;
});
}

@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.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/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart';
import 'package:hmg_patient_app_new/widgets/loading_dialog.dart';
import 'package:lottie/lottie.dart';
@ -340,6 +341,55 @@ class Utils {
).center;
}
static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Function? onConfirmTap, Function? onCancelTap}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Lottie.asset(AppAnimations.warningAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor),
SizedBox(height: 16.h),
isShowActionButtons
? Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
onPressed: () {
if (onCancelTap != null) {
onCancelTap();
}
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
icon: AppAssets.cancel,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(width: 8.h),
Expanded(
child: CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
if (onConfirmTap != null) {
onConfirmTap();
}
},
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),
),
],
)
: SizedBox.shrink(),
],
).center;
}
static bool isVidaPlusProject(int projectID) {
AppState appState = getIt.get<AppState>();
bool isVidaPlus = false;

@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_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/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class BookAppointmentsRepo {
@ -19,6 +20,26 @@ abstract class BookAppointmentsRepo {
Future<Either<Failure, GenericApiModel<DoctorsProfileResponseModel>>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel});
Future<Either<Failure, GenericApiModel<dynamic>>> insertSpecificAppointment(
{required int docID,
required int clinicID,
required int projectID,
required String selectedTime,
required String selectedDate,
required int initialSlotDuration,
required int genderID,
required int userAge,
String? procedureID,
num? testTypeEnum,
num? testProcedureEnum,
int? invoiceNumber,
int? lineItemNo,
String? invoiceNoVP,
Function(dynamic)? onSuccess,
Function(String)? onError});
}
class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@ -201,18 +222,133 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['FreeTimeSlots'];
// if (list == null || list.isEmpty) {
// throw Exception("lab list is empty");
// }
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} 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()));
}
}
//TODO: Handle the cases for LiveCare Schedule, Dental & Laser Clinics
@override
Future<Either<Failure, GenericApiModel>> insertSpecificAppointment(
{required int docID,
required int clinicID,
required int projectID,
required String selectedTime,
required String selectedDate,
required int initialSlotDuration,
required int genderID,
required int userAge,
String? procedureID,
num? testTypeEnum,
num? testProcedureEnum,
int? invoiceNumber,
int? lineItemNo,
String? invoiceNoVP,
Function(dynamic p1)? onSuccess,
Function(String p1)? onError}) async {
// {"AppointmentID":2016072771,"ClinicID":1485,"ProjectID":15,"CancelToReschadual":true,"EndTime":"21:15:00","StartTime":"21:00:00","DoctorID":1485,"IsForLiveCare":false,"OriginalClinicID":0,"OriginalProjectID":0,"StrAppointmentDate":"Tuesday, 16 September 2025","VersionID":19.0,"Channel":3,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"jTarYomiIEKOruN8Z4YINA==","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4770714,"PatientTypeID":1,"PatientType":1,"LanguageID":2,"Latitude":24.698381,"Longitude":46.6804279,"TokenID":"jTarYomiIEKOruN8Z4YINA=="}
Map<String, dynamic> mapDevice = {
"IsForLiveCare": false,
"ProjectID": projectID,
"ClinicID": clinicID,
"DoctorID": docID,
"StartTime": selectedTime,
"SelectedTime": selectedTime,
"EndTime": selectedTime,
"ProcedureID": procedureID,
"TestTypeEnum": testTypeEnum,
"TestProcedureEnum": testProcedureEnum,
"InitialSlotDuration": initialSlotDuration,
"StrAppointmentDate": selectedDate,
"IsVirtual": false,
"BookedBy": 102,
"VisitType": 1,
"VisitFor": 1,
"GenderID": genderID,
"Age": userAge,
"InvoiceNo": invoiceNumber,
"InvoiceNo_VP": invoiceNoVP,
"LineItemNo": lineItemNo,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
INSERT_SPECIFIC_APPOINTMENT,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final appointmentNo = response['AppointmentNo'];
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: response["ErrorEndUserMessage"],
data: response,
);
} 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()));
}
}
// final freeSlotsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DoctorsListResponseModel>();
@override
Future<Either<Failure, GenericApiModel>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async {
Map<String, dynamic> requestBody = {
"AppointmentID": patientAppointmentHistoryResponseModel.appointmentNo,
"ClinicID": patientAppointmentHistoryResponseModel.clinicID,
"ProjectID": patientAppointmentHistoryResponseModel.projectID,
"CancelToReschadual": false,
"EndTime": patientAppointmentHistoryResponseModel.endTime,
"StartTime": patientAppointmentHistoryResponseModel.startTime,
"DoctorID": patientAppointmentHistoryResponseModel.doctorID,
"IsForLiveCare": patientAppointmentHistoryResponseModel.isLiveCareAppointment,
"OriginalClinicID": patientAppointmentHistoryResponseModel.originalClinicID,
"OriginalProjectID": patientAppointmentHistoryResponseModel.originalProjectID
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
CANCEL_APPOINTMENT,
body: requestBody,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response['FreeTimeSlots'],
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());

@ -1,6 +1,11 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
@ -8,7 +13,14 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model
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/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
class BookAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0;
@ -18,6 +30,8 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isDoctorProfileLoading = false;
bool isDoctorSearchByNameStarted = false;
int initialSlotDuration = 0;
List<GetClinicsListResponseModel> clinicsList = [];
List<GetClinicsListResponseModel> _filteredClinicsList = [];
@ -42,8 +56,12 @@ class BookAppointmentsViewModel extends ChangeNotifier {
BookAppointmentsRepo bookAppointmentsRepo;
ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
MyAppointmentsViewModel myAppointmentsViewModel;
late AppState _appState;
BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService});
BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel});
void initializeFilteredList() {
_filteredClinicsList = List.from(clinicsList);
@ -60,6 +78,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
initBookAppointmentViewModel() {
_appState = getIt<AppState>();
isClinicsListLoading = true;
isDoctorsListLoading = true;
isDoctorProfileLoading = true;
@ -206,12 +225,13 @@ class BookAppointmentsViewModel extends ChangeNotifier {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data == null || apiResponse.data!.isEmpty) {
if (apiResponse.data['FreeTimeSlots'] == null || apiResponse.data['FreeTimeSlots'].isEmpty) {
onError!("No free slots available".tr());
return;
}
freeSlotsResponse = apiResponse.data;
apiResponse.data!.forEach((element) {
initialSlotDuration = apiResponse.data["InitialSlotDuration"];
freeSlotsResponse = apiResponse.data['FreeTimeSlots'];
freeSlotsResponse.forEach((element) {
// date = (isLiveCareSchedule != null && isLiveCareSchedule)
// ? DateUtil.convertStringToDate(element)
// :
@ -228,4 +248,136 @@ class BookAppointmentsViewModel extends ChangeNotifier {
},
);
}
Future<void> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
//TODO: Handle the cases for LiveCare Schedule, Dental & Laser Clinics
Future<void> insertSpecificAppointment(
{
// required int docID,
// required int clinicID,
// required int projectID,
// required String selectedTime,
// required String selectedDate,
// required int initialSlotDuration,
// required int genderID,
// required int userAge,
String? procedureID,
num? testTypeEnum,
num? testProcedureEnum,
int? invoiceNumber,
int? lineItemNo,
String? invoiceNoVP,
Function(dynamic p1)? onSuccess,
Function(dynamic p1)? onError}) async {
_appState = getIt<AppState>();
final result = await bookAppointmentsRepo.insertSpecificAppointment(
docID: selectedDoctor.doctorID!,
clinicID: selectedDoctor.clinicID!,
projectID: selectedDoctor.projectID!,
selectedDate: selectedAppointmentDate,
selectedTime: selectedAppointmentTime,
initialSlotDuration: initialSlotDuration,
genderID: _appState.getAuthenticatedUser()!.gender!,
userAge: _appState.getAuthenticatedUser()!.age!,
onError: onError);
result.fold(
(failure) async {
print(failure);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// onError!(apiResponse);
LoadingUtils.hideFullScreenLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: apiResponse.data["ErrorEndUserMessage"],
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
},
onConfirmTap: () async {
navigationService.pop();
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel(
appointmentNo: apiResponse.data["SameClinicApptList"][0]['AppointmentNo'],
clinicID: apiResponse.data["SameClinicApptList"][0]['ClinicID'],
projectID: apiResponse.data["SameClinicApptList"][0]['ProjectID'],
endDate: apiResponse.data["SameClinicApptList"][0]['EndTime'],
startTime: apiResponse.data["SameClinicApptList"][0]['StartTime'],
doctorID: apiResponse.data["SameClinicApptList"][0]['DoctorID'],
isLiveCareAppointment: apiResponse.data["SameClinicApptList"][0]['IsLiveCareAppointment'],
originalClinicID: 0,
originalProjectID: 0,
appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'],
);
showCommonBottomSheet(navigationService.navigatorKey.currentContext!,
child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation),
callBackFunc: (str) {},
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false);
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
navigationService.pop();
Future.delayed(Duration(milliseconds: 50)).then((value) async {});
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation);
await insertSpecificAppointment(
onError: (err) {},
onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
FadePage(
page: LandingNavigation(),
),
(r) => false);
});
});
});
});
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data == null || apiResponse.data!.isEmpty) {
onError!("No free slots available".tr());
return;
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -128,6 +128,8 @@ void main() async {
create: (_) => BookAppointmentsViewModel(
bookAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt(),
),
),
ChangeNotifierProvider<AuthenticationViewModel>(

@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
@ -18,12 +20,14 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:maps_launcher/maps_launcher.dart';
@ -43,6 +47,7 @@ class AppointmentDetailsPage extends StatefulWidget {
class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@override
void initState() {
@ -59,6 +64,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -123,7 +129,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
onRescheduleTap: () {},
onRescheduleTap: () async {
openDoctorScheduleCalendar();
},
),
SizedBox(height: 16.h),
!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
@ -443,7 +451,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? CustomButton(
text: "Re-book Appointment".needTranslation,
onPressed: () {},
onPressed: () {
openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
@ -482,6 +492,46 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
}
openDoctorScheduleCalendar() async {
DoctorsListResponseModel doctor = DoctorsListResponseModel(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: widget.patientAppointmentHistoryResponseModel.doctorTitle,
name: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: "https://hmgwebservices.com/Images/flag/SYR.png",
speciality: [],
clinicName: widget.patientAppointmentHistoryResponseModel.clinicName,
projectName: widget.patientAppointmentHistoryResponseModel.projectName,
);
bookAppointmentsViewModel.setSelectedDoctor(doctor);
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Pick a Date".needTranslation,
context,
child: AppointmentCalendar(),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}
Future<void> handleAppointmentNextAction(nextAction) async {
switch (nextAction) {
case 0:

@ -97,7 +97,9 @@ class AppointmentDoctorCard extends StatelessWidget {
)
: CustomButton(
text: "Rebook with same doctor".needTranslation,
onPressed: () {},
onPressed: () {
onRescheduleTap();
},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,

@ -310,6 +310,7 @@ class _SavedLogin extends State<SavedLogin> {
2: AppAssets.fingerprint,
3: AppAssets.fingerprint,
4: AppAssets.whatsapp,
0: AppAssets.sms,
};
if (types.containsKey(type)) {

@ -120,40 +120,13 @@ class DoctorProfilePage extends StatelessWidget {
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Pick a Date",
title: "Pick a Date".needTranslation,
context,
child: AppointmentCalendar(),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
// //TODO: Calendar design to be changed as per new design & handle Dubai & KSA both cases
// final DateTimeResult picked = await showHijriGregBottomSheet(context,
// design: Design.v2,
// isShowTimeSlots: true,
// height: 750.h,
// dateTimeSlots: bookAppointmentsViewModel.freeSlotsResponse,
// showCalendarToggle: false,
// switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
// language: appState.getLanguageCode()!,
// initialDate: DateUtil.convertStringToDate(bookAppointmentsViewModel.freeSlotsResponse.first),
// okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
// cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
// onCalendarTypeChanged: (bool value) {
// // isGregorian = true;
// });
// if (picked != null) {
// print("Selected Date & Time:");
// print(picked.date.toIso8601String());
// String formattedTime = '${picked.time.hour.toString().padLeft(2, '0')}:${picked.time.minute.toString().padLeft(2, '0')}';
// print(formattedTime);
//
// bookAppointmentsViewModel.setSelectedAppointmentDateTime(picked.date.toIso8601String().split("T")[0], formattedTime);
// } else {
// print("User cancelled the picker");
// return;
// }
},
onError: (err) {
LoaderBottomSheet.hideLoader();

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -10,10 +11,12 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
class ReviewAppointmentPage extends StatefulWidget {
@ -104,7 +107,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}".needTranslation,
),
AppCustomChipWidget(
labelText: "${LocaleKeys.branch.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation,
labelText: "${LocaleKeys.branch.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation,
),
AppCustomChipWidget(
labelText: "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.selectedAppointmentDate}".needTranslation,
@ -197,7 +200,37 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
);
}
void initiateBookAppointment() {
void initiateBookAppointment() async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation);
await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) {
print(err.data["ErrorEndUserMessage"]);
LoadingUtils.hideFullScreenLoader();
}, onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
Navigator.pushAndRemoveUntil(
context,
FadePage(
page: LandingNavigation(),
),
(r) => false);
});
});
});
// await Future.delayed(Duration(milliseconds: 4000)).then((value) async {
// LoadingUtils.hideFullScreenLoader();
// await Future.delayed(Duration(milliseconds: 50)).then((value) async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
// await Future.delayed(Duration(milliseconds: 4000)).then((value) {
// LoadingUtils.hideFullScreenLoader();
// });
// });
// });
}
}

@ -99,6 +99,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
? DoctorCard(
doctorsListResponseModel: DoctorsListResponseModel(),
isLoading: true,
bookAppointmentsViewModel: bookAppointmentsViewModel,
)
: AnimationConfiguration.staggeredList(
position: index,
@ -113,6 +114,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.doctorsList[index],
isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());

@ -101,6 +101,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
? DoctorCard(
doctorsListResponseModel: DoctorsListResponseModel(),
isLoading: true,
bookAppointmentsViewModel: bookAppointmentsViewModel,
)
: AnimationConfiguration.staggeredList(
position: index,
@ -115,6 +116,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.doctorsList[index],
isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());

@ -8,7 +8,6 @@ import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';

@ -5,17 +5,22 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/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/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class DoctorCard extends StatelessWidget {
DoctorCard({super.key, required this.doctorsListResponseModel, required this.isLoading});
DoctorCard({super.key, required this.doctorsListResponseModel, required this.isLoading, required this.bookAppointmentsViewModel});
DoctorsListResponseModel doctorsListResponseModel;
bool isLoading = false;
BookAppointmentsViewModel bookAppointmentsViewModel;
@override
Widget build(BuildContext context) {
@ -100,7 +105,33 @@ class DoctorCard extends StatelessWidget {
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {},
onPressed: () async {
bookAppointmentsViewModel.setSelectedDoctor(doctorsListResponseModel);
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Pick a Date".needTranslation,
context,
child: AppointmentCalendar(),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),

@ -24,6 +24,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.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/widgets/buttons/custom_button.dart';
@ -90,7 +91,13 @@ class _LandingPageState extends State<LandingPage> {
children: [
appState.isAuthenticated
? WelcomeWidget(
onTap: () {},
onTap: () {
Navigator.of(context).push(
FadePage(
page: ProfileSettings(),
),
);
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageUrl: appState
.getAuthenticatedUser()

@ -11,6 +11,8 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/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/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
@ -20,6 +22,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/generated/locale_keys.g.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/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
@ -33,8 +36,10 @@ import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_lis
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
@ -54,6 +59,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late AppState appState;
late MyAppointmentsViewModel myAppointmentsViewModel;
late MedicalFileViewModel medicalFileViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
int currentIndex = 0;
@ -74,6 +80,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
@ -245,6 +252,46 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}
openDoctorScheduleCalendar(PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel) async {
DoctorsListResponseModel doctor = DoctorsListResponseModel(
clinicID: patientAppointmentHistoryResponseModel.clinicID,
projectID: patientAppointmentHistoryResponseModel.projectID,
doctorID: patientAppointmentHistoryResponseModel.doctorID,
doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle,
name: patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: "https://hmgwebservices.com/Images/flag/SYR.png",
speciality: [],
clinicName: patientAppointmentHistoryResponseModel.clinicName,
projectName: patientAppointmentHistoryResponseModel.projectName,
);
bookAppointmentsViewModel.setSelectedDoctor(doctor);
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Pick a Date".needTranslation,
context,
child: AppointmentCalendar(),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}
Widget getSelectedTabData(int index) {
switch (index) {
case 0:
@ -293,10 +340,16 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsVM,
onRescheduleTap: () {},
onAskDoctorTap: () {},
)
: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () {},
),
),
),

@ -16,11 +16,14 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
class MedicalFileAppointmentCard extends StatelessWidget {
MedicalFileAppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel});
MedicalFileAppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel, required this.onRescheduleTap, required this.onAskDoctorTap});
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
MyAppointmentsViewModel myAppointmentsViewModel;
late Function onRescheduleTap;
late Function onAskDoctorTap;
@override
Widget build(BuildContext context) {
return Column(
@ -164,7 +167,9 @@ class MedicalFileAppointmentCard extends StatelessWidget {
)
: CustomButton(
text: "Rebook".needTranslation,
onPressed: () {},
onPressed: () {
onRescheduleTap();
},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,

Loading…
Cancel
Save