Merge pull request 'dev_sultan' (#119) from dev_sultan into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/119
pull/123/head
Haroon6138 1 month ago
commit ceeeea59e3

@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -141,6 +142,9 @@ class AppDependencies {
getIt.registerLazySingleton<MyAppointmentsViewModel>(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<AppointmentRatingViewModel>(
() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<PayfortViewModel>(
() => PayfortViewModel(
payfortRepo: getIt(),

@ -262,7 +262,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
newRequest.forRegisteration = newRequest.isRegister ?? false;
newRequest.isRegister = false;
//silent login case removed token and login token
// if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true) {
// if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true && (newRequest.loginType==1 || newRequest.loginType==4)) {
// newRequest.logInTokenID = null;
// newRequest.deviceToken = null;
// }

@ -0,0 +1,134 @@
// dart
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_details_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'models/resp_models/rate_appointment_resp_model.dart';
class AppointmentRatingViewModel extends ChangeNotifier {
final MyAppointmentsRepo myAppointmentsRepo;
final ErrorHandlerService errorHandlerService;
final AppState appState;
List<RateAppointmentRespModel> appointmentRatedList = [];
AppointmentDetails? appointmentDetails;
AppointmentRatingViewModel({
required this.myAppointmentsRepo,
required this.errorHandlerService,
required this.appState,
});
String title = "";
String subTitle = "";
bool isRateClinic = false;
Future<void> getLastRatingAppointment({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getLastRatingAppointment();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError?.call(apiResponse.errorMessage ?? 'Unknown error');
} else if (apiResponse.messageStatus == 1) {
appointmentRatedList = apiResponse.data ?? [];
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data);
}
}
},
);
}
Future<void> getAppointmentDetails(int appointmentID, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getAppointmentDetails(appointmentID, projectID);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError?.call(apiResponse.errorMessage ?? 'Unknown error');
} else if (apiResponse.messageStatus == 1) {
appointmentDetails = apiResponse.data ?? AppointmentDetails();
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data);
}
}
},
);
}
Future<void> submitDoctorRating( {required int docRate, required String docNote,Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.sendDoctorRate(
docRate,
appointmentDetails!.appointmentNo!,
appointmentDetails!.projectID!,
appointmentDetails!.doctorID!,
appointmentDetails!.clinicID!,
docNote,
appointmentDetails!.appointmentDate!,
appointmentDetails!.doctorName,
appointmentDetails!.projectName,
appointmentDetails!.clinicName
);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError?.call(apiResponse.errorMessage ?? 'Unknown error');
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
// onSuccess(apiResponse.data);
}
}
},
);
}
Future<void> submitClinicRating( { required int clinicRate, required String clinicNote, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.sendAppointmentRate(
clinicRate,
appointmentDetails!.appointmentNo!,
appointmentDetails!.projectID!,
appointmentDetails!.doctorID!,
appointmentDetails!.clinicID!,
clinicNote
);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError?.call(apiResponse.errorMessage ?? 'Unknown error');
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
// onSuccess(apiResponse.data);
}
}
},
);
}
void setSubTitle(String value) {
this.subTitle = value;
notifyListeners();
}
void setTitle(String value) {
this.title = value;
notifyListeners();
}
void setClinicOrDoctor(bool value){
this.isRateClinic = value;
notifyListeners();
}
}

@ -0,0 +1,100 @@
class AppointmentRate {
int? rate;
int? appointmentNo;
int? projectID;
int? doctorID;
int? clinicID;
String? note;
String? mobileNumber;
int? createdBy;
int? editedBy;
double? versionID;
int? channel;
int? languageID;
String? iPAdress;
String? generalid;
int? patientOutSA;
String? sessionID;
bool? isDentalAllowedBackend;
int? deviceTypeID;
int? patientID;
String? tokenID;
int? patientTypeID;
int? patientType;
AppointmentRate(
{this.rate,
this.appointmentNo,
this.projectID,
this.doctorID,
this.clinicID,
this.note,
this.mobileNumber,
this.createdBy,
this.editedBy,
this.versionID,
this.channel,
this.languageID,
this.iPAdress,
this.generalid,
this.patientOutSA,
this.sessionID,
this.isDentalAllowedBackend,
this.deviceTypeID,
this.patientID,
this.tokenID,
this.patientTypeID,
this.patientType});
AppointmentRate.fromJson(Map<String, dynamic> json) {
rate = json['Rate'];
appointmentNo = json['AppointmentNo'];
projectID = json['ProjectID'];
doctorID = json['DoctorID'];
clinicID = json['ClinicID'];
note = json['Note'];
mobileNumber = json['MobileNumber'];
createdBy = json['CreatedBy'];
editedBy = json['EditedBy'];
versionID = json['VersionID'];
channel = json['Channel'];
languageID = json['LanguageID'];
iPAdress = json['IPAdress'];
generalid = json['generalid'];
patientOutSA = json['PatientOutSA'];
sessionID = json['SessionID'];
isDentalAllowedBackend = json['isDentalAllowedBackend'];
deviceTypeID = json['DeviceTypeID'];
patientID = json['PatientID'];
tokenID = json['TokenID'];
patientTypeID = json['PatientTypeID'];
patientType = json['PatientType'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['Rate'] = this.rate;
data['AppointmentNo'] = this.appointmentNo;
data['ProjectID'] = this.projectID;
data['DoctorID'] = this.doctorID;
data['ClinicID'] = this.clinicID;
data['Note'] = this.note;
data['MobileNumber'] = this.mobileNumber;
data['CreatedBy'] = this.createdBy;
data['EditedBy'] = this.editedBy;
data['VersionID'] = this.versionID;
data['Channel'] = this.channel;
data['LanguageID'] = this.languageID;
data['IPAdress'] = this.iPAdress;
data['generalid'] = this.generalid;
data['PatientOutSA'] = this.patientOutSA;
data['SessionID'] = this.sessionID;
data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
data['DeviceTypeID'] = this.deviceTypeID;
data['PatientID'] = this.patientID;
data['TokenID'] = this.tokenID;
data['PatientTypeID'] = this.patientTypeID;
data['PatientType'] = this.patientType;
return data;
}
}

@ -0,0 +1,64 @@
class AppointmentDetails {
String? setupID;
int? projectID;
int? patientID;
int? appointmentNo;
int? clinicID;
int? doctorID;
dynamic startTime;
dynamic endTime;
dynamic appointmentDate;
dynamic clinicName;
dynamic doctorImageURL;
dynamic doctorName;
dynamic projectName;
AppointmentDetails(
{this.setupID,
this.projectID,
this.patientID,
this.appointmentNo,
this.clinicID,
this.doctorID,
this.startTime,
this.endTime,
this.appointmentDate,
this.clinicName,
this.doctorImageURL,
this.doctorName,
this.projectName});
AppointmentDetails.fromJson(Map<String, dynamic> json) {
setupID = json['SetupID'];
projectID = json['ProjectID'];
patientID = json['PatientID'];
appointmentNo = json['AppointmentNo'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
startTime = json['StartTime'];
endTime = json['EndTime'];
appointmentDate = json['AppointmentDate'];
clinicName = json['ClinicName'];
doctorImageURL = json['DoctorImageURL'];
doctorName = json['DoctorName'];
projectName = json['ProjectName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['ProjectID'] = this.projectID;
data['PatientID'] = this.patientID;
data['AppointmentNo'] = this.appointmentNo;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['StartTime'] = this.startTime;
data['EndTime'] = this.endTime;
data['AppointmentDate'] = this.appointmentDate;
data['ClinicName'] = this.clinicName;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorName'] = this.doctorName;
data['ProjectName'] = this.projectName;
return data;
}
}

@ -0,0 +1,160 @@
class RateAppointmentRespModel {
String? setupID;
int? projectID;
int? appointmentNo;
String? appointmentDate;
String? appointmentDateN;
int? appointmentType;
String? bookDate;
int? patientType;
int? patientID;
int? clinicID;
int? doctorID;
String? endDate;
String? startTime;
String? endTime;
int? status;
int? visitType;
int? visitFor;
int? patientStatusType;
int? companyID;
int? bookedBy;
String? bookedOn;
int? confirmedBy;
String? confirmedOn;
int? arrivalChangedBy;
String? arrivedOn;
int? editedBy;
String? editedOn;
dynamic doctorName;
String? doctorNameN;
String? statusDesc;
String? statusDescN;
bool? vitalStatus;
dynamic vitalSignAppointmentNo;
int? episodeID;
String? doctorTitle;
bool? isAppoitmentLiveCare;
RateAppointmentRespModel(
{this.setupID,
this.projectID,
this.appointmentNo,
this.appointmentDate,
this.appointmentDateN,
this.appointmentType,
this.bookDate,
this.patientType,
this.patientID,
this.clinicID,
this.doctorID,
this.endDate,
this.startTime,
this.endTime,
this.status,
this.visitType,
this.visitFor,
this.patientStatusType,
this.companyID,
this.bookedBy,
this.bookedOn,
this.confirmedBy,
this.confirmedOn,
this.arrivalChangedBy,
this.arrivedOn,
this.editedBy,
this.editedOn,
this.doctorName,
this.doctorNameN,
this.statusDesc,
this.statusDescN,
this.vitalStatus,
this.vitalSignAppointmentNo,
this.episodeID,
this.doctorTitle,
this.isAppoitmentLiveCare});
RateAppointmentRespModel.fromJson(Map<String, dynamic> json) {
try {
setupID = json['SetupID'];
projectID = json['ProjectID'];
appointmentNo = json['AppointmentNo'];
appointmentDate = json['AppointmentDate'];
appointmentDateN = json['AppointmentDateN'];
appointmentType = json['AppointmentType'];
bookDate = json['BookDate'];
patientType = json['PatientType'];
patientID = json['PatientID'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
endDate = json['EndDate'];
startTime = json['StartTime'];
endTime = json['EndTime'];
status = json['Status'];
visitType = json['VisitType'];
visitFor = json['VisitFor'];
patientStatusType = json['PatientStatusType'];
companyID = json['CompanyID'];
bookedBy = json['BookedBy'];
bookedOn = json['BookedOn'];
confirmedBy = json['ConfirmedBy'];
confirmedOn = json['ConfirmedOn'];
arrivalChangedBy = json['ArrivalChangedBy'];
arrivedOn = json['ArrivedOn'];
editedBy = json['EditedBy'];
editedOn = json['EditedOn'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
statusDesc = json['StatusDesc'];
statusDescN = json['StatusDescN'];
vitalStatus = json['VitalStatus'];
vitalSignAppointmentNo = json['VitalSignAppointmentNo'];
episodeID = json['EpisodeID'];
doctorTitle = json['DoctorTitle'];
isAppoitmentLiveCare = json['IsAppoitmentLiveCare'];
} catch (e) {
print(e);
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['ProjectID'] = this.projectID;
data['AppointmentNo'] = this.appointmentNo;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentDateN'] = this.appointmentDateN;
data['AppointmentType'] = this.appointmentType;
data['BookDate'] = this.bookDate;
data['PatientType'] = this.patientType;
data['PatientID'] = this.patientID;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['EndDate'] = this.endDate;
data['StartTime'] = this.startTime;
data['EndTime'] = this.endTime;
data['Status'] = this.status;
data['VisitType'] = this.visitType;
data['VisitFor'] = this.visitFor;
data['PatientStatusType'] = this.patientStatusType;
data['CompanyID'] = this.companyID;
data['BookedBy'] = this.bookedBy;
data['BookedOn'] = this.bookedOn;
data['ConfirmedBy'] = this.confirmedBy;
data['ConfirmedOn'] = this.confirmedOn;
data['ArrivalChangedBy'] = this.arrivalChangedBy;
data['ArrivedOn'] = this.arrivedOn;
data['EditedBy'] = this.editedBy;
data['EditedOn'] = this.editedOn;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['StatusDesc'] = this.statusDesc;
data['StatusDescN'] = this.statusDescN;
data['VitalStatus'] = this.vitalStatus;
data['VitalSignAppointmentNo'] = this.vitalSignAppointmentNo;
data['EpisodeID'] = this.episodeID;
data['DoctorTitle'] = this.doctorTitle;
data['IsAppoitmentLiveCare'] = this.isAppoitmentLiveCare;
return data;
}
}

@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
@ -16,6 +17,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'models/req_model/appointment_rate_req_model.dart';
import 'models/resp_models/appointment_details_resp_model.dart';
abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false});
@ -60,6 +64,15 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> sendAskDocCallRequest(
{required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber});
Future<Either<Failure, GenericApiModel<List<RateAppointmentRespModel>>>> getLastRatingAppointment();
Future<Either<Failure, GenericApiModel<AppointmentDetails>>> getAppointmentDetails(int appointmentID, int projectID);
Future<Either<Failure, GenericApiModel<void>>> sendAppointmentRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note);
Future<Either<Failure, GenericApiModel<void>>> sendDoctorRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName);
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID});
}
@ -70,7 +83,8 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
MyAppointmentsRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments(
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async {
Map<String, dynamic> mapDevice = {
"IsActiveAppointment": isActiveAppointment,
"IsComingFromCOC": false,
@ -179,7 +193,9 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
"AppointmentNo": appointmentNo,
"PaymentMethodName": paymentMethodName,
"PaymentAmount": payedAmount == 0 ? "0" : payedAmount.toString(),
"PaymentDate": payedAmount == 0 ? "" : "/Date(${DateTime.now().millisecondsSinceEpoch})/",
"PaymentDate": payedAmount == 0 ? "" : "/Date(${DateTime
.now()
.millisecondsSinceEpoch})/",
"PaymentReferenceNumber": payedAmount == 0 ? "" : paymentReference,
"ProjectID": projectID,
"PatientID": patientID,
@ -780,4 +796,166 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<RateAppointmentRespModel>>>> getLastRatingAppointment() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<RateAppointmentRespModel>>? apiResponse;
Failure? failure;
await apiClient.post(IS_LAST_APPOITMENT_RATED, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['IsLastAppoitmentRatedList'];
final lstRatingAppointmentList = list.map((item) => RateAppointmentRespModel.fromJson(item as Map<String, dynamic>)).toList().cast<RateAppointmentRespModel>();
apiResponse = GenericApiModel<List<RateAppointmentRespModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: lstRatingAppointmentList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, body: mapDevice);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<AppointmentDetails>>> getAppointmentDetails(int appointmentID, int projectID) async {
Map<String, dynamic> mapDevice = {
"AppointmentNumber": appointmentID,
"ProjectID": projectID,
};
try {
GenericApiModel<AppointmentDetails>? apiResponse;
Failure? failure;
await apiClient.post(GET_APPOINTMENT_DETAILS_BY_NO, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['AppointmentDetails'];
final appointmentDetails = AppointmentDetails.fromJson(list);
apiResponse = GenericApiModel<AppointmentDetails>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentDetails,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, body: mapDevice);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<void>>> sendAppointmentRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note) async {
AppointmentRate appointmentRate = AppointmentRate();
appointmentRate.rate = rate;
appointmentRate.appointmentNo = appointmentNo;
appointmentRate.projectID = projectID;
appointmentRate.doctorID = doctorID;
appointmentRate.clinicID = clinicID;
appointmentRate.note = note;
appointmentRate.createdBy = 2;
appointmentRate.editedBy = 2;
try {
GenericApiModel<bool>? apiResponse;
Failure? failure;
await apiClient.post(NEW_RATE_APPOINTMENT_URL, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['AppointmentRated'];
apiResponse = GenericApiModel<bool>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: list,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, body: appointmentRate.toJson());
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<void>>> sendDoctorRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName) async {
Map<String, dynamic> request;
request = {
"DoctorID": doctorID,
"Rate": rate,
"ClinicID": clinicID,
"ProjectID": projectID,
"AppointmentNo": appointmentNo,
"Note": note,
// "MobileNumber": authenticatedUserObject.user!.mobileNumber,
"AppointmentDate": appoDate,
"DoctorName": docName,
"ProjectName": projectName,
"COCTypeName": 1,
// "PatientName": authenticatedUserObject.user!.firstName! + " " + authenticatedUserObject.user!.lastName!,
// "PatientOutSA": authenticatedUserObject.user!.outSA,
// "PatientTypeID": authenticatedUserObject.user!.patientType,
"ClinicName": clinicName,
// "PatientIdentificationID": authenticatedUserObject.user!.patientIdentificationNo
};
try {
GenericApiModel<bool>? apiResponse;
Failure? failure;
await apiClient.post(NEW_RATE_DOCTOR_URL, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['AppointmentRated'];
// final appointmentDetails = AppointmentDetails.fromJson(list);
apiResponse = GenericApiModel<bool>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: list,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, body: request);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -44,6 +44,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientQueueDetails> patientQueueDetailsList = [];
late PatientQueueDetails currentPatientQueueDetails;
List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList = [];
@ -740,4 +743,5 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
}
}

@ -22,6 +22,7 @@ import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.d
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
@ -108,6 +109,9 @@ void main() async {
ChangeNotifierProvider<MyAppointmentsViewModel>(
create: (_) => getIt.get<MyAppointmentsViewModel>(),
),
ChangeNotifierProvider<AppointmentRatingViewModel>(
create: (_) => getIt.get<AppointmentRatingViewModel>(),
),
ChangeNotifierProvider<PayfortViewModel>(
create: (_) => getIt.get<PayfortViewModel>(),
),

@ -39,7 +39,7 @@ class AppointmentCard extends StatelessWidget {
final MedicalFileViewModel? medicalFileViewModel;
final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel;
final bool isForRate;
const AppointmentCard({
super.key,
required this.patientAppointmentHistoryResponseModel,
@ -52,6 +52,7 @@ class AppointmentCard extends StatelessWidget {
this.isForFeedback = false,
this.medicalFileViewModel,
this.contactUsViewModel,
this.isForRate =false
});
@override
@ -64,11 +65,11 @@ class AppointmentCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context, appState),
isForRate ? SizedBox(): _buildHeader(context, appState),
SizedBox(height: 16.h),
_buildDoctorRow(context),
SizedBox(height: 16.h),
_buildActionArea(context, appState),
isForRate ? SizedBox(): _buildActionArea(context, appState),
],
),
),

@ -21,6 +21,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_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/prescriptions/prescriptions_view_model.dart';
@ -43,6 +44,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -69,7 +71,7 @@ class _LandingPageState extends State<LandingPage> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
late AppointmentRatingViewModel appointmentRatingViewModel;
late InsuranceViewModel insuranceViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@ -81,6 +83,7 @@ class _LandingPageState extends State<LandingPage> {
void initState() {
authVM = context.read<AuthenticationViewModel>();
habibWalletVM = context.read<HabibWalletViewModel>();
appointmentRatingViewModel = context.read<AppointmentRatingViewModel>();
// myAppointmentsViewModel = context.read<MyAppointmentsViewModel>();
// prescriptionsViewModel = context.read<PrescriptionsViewModel>();
// insuranceViewModel = context.read<InsuranceViewModel>();
@ -104,6 +107,17 @@ class _LandingPageState extends State<LandingPage> {
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
emergencyServicesViewModel.checkPatientERAdvanceBalance();
appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) {
if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!,
onSuccess: ((response) {
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation);
appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation);
openLastRating();
}));
}
});
}
});
super.initState();
@ -453,17 +467,14 @@ class _LandingPageState extends State<LandingPage> {
)
: Container(
width: double.infinity,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
SizedBox(height: 12.h),
"You do not have any upcoming appointment. Please book an appointment"
.needTranslation
.toText12(isCenter: true),
"You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
@ -721,4 +732,31 @@ class _LandingPageState extends State<LandingPage> {
},
);
}
openLastRating() {
showCommonBottomSheetWithoutHeight(
context,
titleWidget: Selector<AppointmentRatingViewModel, String?>(
selector: (_, vm) => vm.title,
builder: (context, title, child) {
final displayTitle = title ?? '';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
displayTitle.toText20(weight: FontWeight.w600),
(context.select<AppointmentRatingViewModel, String?>((vm) => vm.subTitle) ?? '').toText12(),
],
);
},
),
isCloseButtonVisible: true,
child: StatefulBuilder(
builder: (context, setState) {
return RateAppointmentDoctor();
},
),
isFullScreen: false,
);
}
}

@ -0,0 +1,212 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_patient_app_new/core/utils/size_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/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.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/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class RateAppointmentClinic extends StatefulWidget {
late final String? doctorNote;
late final int? doctorRate;
RateAppointmentClinic({this.doctorRate, this.doctorNote});
@override
_RateAppointmentClinicState createState() => _RateAppointmentClinicState();
}
class _RateAppointmentClinicState extends State<RateAppointmentClinic> {
final formKey = GlobalKey<FormState>();
String note = "";
int rating = 5;
AppointmentRatingViewModel? appointmentRatingViewModel;
MyAppointmentsViewModel? myAppointmentsViewModel;
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
appointmentRatingViewModel = Provider.of<AppointmentRatingViewModel>(context, listen: false);
// Make the sheet a fixed height and keep content scrollable while pinning buttons to bottom
final sheetHeight = ResponsiveExtension.screenHeight * 0.60;
return SizedBox(
height: sheetHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Scrollable content
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Doctor row
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: BuildDoctorRow(
isForClinic: true,
appointmentDetails: appointmentRatingViewModel!.appointmentDetails,
),
),
SizedBox(height: 16),
// Rate clinic box
SizedBox(
width: double.infinity,
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Rate Clinic".needTranslation.toText16(isBold: true),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...List.generate(
5,
(index) => rating == (index + 1)
? Container(
margin: EdgeInsets.only(left: 3.0, right: 3.0),
child: IconButton(
onPressed: () {
setState(() {
rating = index + 1;
});
},
iconSize: 35,
icon: SvgPicture.asset('assets/images/svg/rate_${index + 1}.svg', colorFilter: getColors(rating)),
),
)
: IconButton(
onPressed: () {
setState(() {
rating = index + 1;
});
},
iconSize: 35,
icon: SvgPicture.asset('assets/images/svg/rate_${index + 1}.svg'),
),
),
],
),
],
),
),
),
),
SizedBox(height: 12),
// Extra content area (keeps any other widgets that were previously below)
Container(
padding: EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Placeholder for in-content widgets if needed in future
],
),
),
// Add bottom spacing so last content isn't obscured by the fixed buttons
SizedBox(height: 12),
],
),
),
),
// Bottom action buttons pinned to bottom of the sheet
SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric( vertical: 12.0),
child: Row(
children: [
Expanded(
child: CustomButton(
text: "Back".needTranslation,
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
onPressed: () {
appointmentRatingViewModel!.setTitle("Rate Doctor".needTranslation);
appointmentRatingViewModel!.setSubTitle("How was your last visit with doctor?".needTranslation);
appointmentRatingViewModel!.setClinicOrDoctor(false);
setState(() {
});
},
),
),
SizedBox(width: 10),
Expanded(
child: CustomButton(
text: "Submit".needTranslation,
onPressed: () {
submitRating();
},
),
),
],
),
),
),
],
),
);
}
ColorFilter getColors(int rating){
switch(rating){
case 5:
return ColorFilter.mode(AppColors.bgGreenColor, BlendMode.srcIn);
case 4:
return ColorFilter.mode(Colors.greenAccent, BlendMode.srcIn);
case 3:
return ColorFilter.mode(AppColors.warningLightColor, BlendMode.srcIn);
case 2:
return ColorFilter.mode(Colors.orange, BlendMode.srcIn);
case 1:
return ColorFilter.mode(AppColors.primaryRedColor, BlendMode.srcIn);
default:
return ColorFilter.mode(AppColors.greyColor, BlendMode.srcIn);
}
}
submitRating() async{
LoaderBottomSheet.showLoader();
await appointmentRatingViewModel!.submitDoctorRating(docRate: widget.doctorRate!, docNote: widget.doctorNote!);
await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note);
LoaderBottomSheet.hideLoader();
Navigator.pop(context);
}
}

@ -0,0 +1,225 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_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/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_clinic.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.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/transitions/fade_page.dart';
import 'package:provider/provider.dart';
class RateAppointmentDoctor extends StatefulWidget {
bool isFromRegistration;
RateAppointmentDoctor({Key? key, this.isFromRegistration = false}) : super(key: key);
@override
_RateAppointmentDoctorState createState() => _RateAppointmentDoctorState();
}
class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
final formKey = GlobalKey<FormState>();
String note = "";
int rating = 5;
// ProjectViewModel? projectViewModel;
AppointmentRatingViewModel? appointmentRatingViewModel;
MyAppointmentsViewModel? myAppointmentsViewModel;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
appointmentRatingViewModel = Provider.of<AppointmentRatingViewModel>(context, listen: false);
final sheetHeight = ResponsiveExtension.screenHeight * 0.60;
return Selector<AppointmentRatingViewModel, bool>(
selector: (_, vm) => vm.isRateClinic,
builder: (context, isRateClinic, child) => isRateClinic
? RateAppointmentClinic(doctorNote: note, doctorRate: rating,)
: SizedBox(
height: sheetHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Scrollable main content
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Doctor row
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: BuildDoctorRow(
isForClinic: false,
appointmentDetails: appointmentRatingViewModel!.appointmentDetails,
)),
SizedBox(height: 16),
// Rating box
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Please rate the doctor".needTranslation.toText16(isBold: true),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...List.generate(
5,
(index) => AnimatedSwitcher(
duration: Duration(milliseconds: 1000),
switchInCurve: Curves.elasticOut,
switchOutCurve: Curves.elasticIn,
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Container(
key: ValueKey<int>(rating),
child: IconButton(
iconSize: 45.0,
onPressed: () {
setState(() {
rating = index + 1;
});
},
color: rating >= (index + 1)
? Color.fromRGBO(255, 186, 0, 1.0)
: Colors.grey[400],
icon: Icon(rating >= (index + 1) ? Icons.star : Icons.star)),
),
),
)
],
),
],
),
),
),
SizedBox(height: 12),
// Note text field
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.0),
child: TextField(
maxLines: 4,
decoration: InputDecoration.collapsed(
hintText: "Notes".needTranslation,
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xff2B353E),
letterSpacing: -0.64,
height: 23 / 16)),
onChanged: (value) {
setState(() {
note = value;
});
},
))),
],
),
),
),
// Bottom action buttons pinned to bottom
SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
children: [
Expanded(
child: CustomButton(
text: "Later".needTranslation,
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
onPressed: () {
Navigator.pop(context);
},
),
),
SizedBox(width: 10),
Expanded(
child: CustomButton(
text: "Next".needTranslation,
onPressed: () {
// Set up clinic rating and show clinic rating view
appointmentRatingViewModel!.setTitle("Rate Clinic".needTranslation);
appointmentRatingViewModel!.setSubTitle("How was your appointment?".needTranslation);
appointmentRatingViewModel!.setClinicOrDoctor(true);
setState(() {});
},
),
),
],
),
),
),
],
),
));
// DoctorList getDoctorObject(AppointmentRateViewModel model) {
// DoctorList doctor = new DoctorList();
//
// doctor.name = model.appointmentDetails.doctorName;
// doctor.doctorImageURL = model.appointmentDetails.doctorImageURL;
// doctor.clinicName = model.appointmentDetails.clinicName;
// doctor.projectName = model.appointmentDetails.projectName;
// doctor.date = model.appointmentDetails.appointmentDate;
// doctor.actualDoctorRate = 5;
//
// return doctor;
// }
}
}

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_details_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
class BuildDoctorRow extends StatelessWidget {
bool isForClinic = false;
AppointmentDetails? appointmentDetails;
BuildDoctorRow({super.key, required this.isForClinic, this.appointmentDetails});
@override
Widget build(BuildContext context) {
return Padding(padding: EdgeInsets.all(16),child:Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
isForClinic ? 'https://hmgwebservices.com/Images/Hospitals/${appointmentDetails!.projectID}.jpg' : appointmentDetails!.doctorImageURL ,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 16.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(isForClinic ? appointmentDetails!.projectName : appointmentDetails!.doctorName)!.toString()
.toText16(isBold: true, maxlines: 1),
SizedBox(height: 8.h),
isForClinic ? Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText:
appointmentDetails!.clinicName.toString(),
),
AppCustomChipWidget(
icon: AppAssets.ic_date_filter,
labelText:
DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointmentDetails!.appointmentDate), false),
),
AppCustomChipWidget(
icon: AppAssets.appointment_time_icon,
labelText:
appointmentDetails!.startTime.substring(0, appointmentDetails!.startTime.length - 3),
),
]
) : Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText:
appointmentDetails!.projectName.toString(),
),
AppCustomChipWidget(
labelText:
appointmentDetails!.clinicName.toString(),
)
]
)
],
),
),
],
));
}
}
Loading…
Cancel
Save