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

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/205
master
Haroon6138 9 hours ago
commit 9612b0019c

File diff suppressed because it is too large Load Diff

@ -908,6 +908,7 @@
"general": "عام",
"liveCare": "لايف كير",
"recentVisits": "الزيارات الأخيرة",
"favouriteDoctors": "الأطباء المفضلون",
"searchByClinic": "البحث حسب العيادة",
"tapToSelectClinic": "انقر لاختيار العيادة",
"searchByDoctor": "البحث حسب الطبيب",

@ -902,6 +902,7 @@
"general": "General",
"liveCare": "LiveCare",
"recentVisits": "Recent Visits",
"favouriteDoctors": "Favourite Doctors",
"searchByClinic": "Search By Clinic",
"tapToSelectClinic": "Tap to select clinic",
"searchByDoctor": "Search By Doctor",

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.preProd;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -229,6 +229,7 @@ class ApiConsts {
static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails";
static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation";
static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New";
static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API";
static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API";
@ -468,6 +469,15 @@ var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefC
//URL to get doctor free slots
var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots";
//URL to check if doctor is favorite
var IS_FAVOURITE_DOCTOR = "Services/Patients.svc/REST/Patient_IsFavouriteDoctor";
//URL to get favorite doctors list
var GET_FAVOURITE_DOCTOR = "Services/Patients.svc/REST/Patient_GetFavouriteDoctor";
//URL to insert favorite doctor
var INSERT_FAVOURITE_DOCTOR = "Services/Patients.svc/REST/Patient_InsertFavouriteDoctor";
//URL to insert appointment
var INSERT_SPECIFIC_APPOINTMENT = "Services/Doctors.svc/REST/InsertSpecificAppointment";

@ -182,6 +182,8 @@ class AppAssets {
static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg';
static const String doctor_profile_rating_icon = '$svgBasePath/doctor_profile_rating_icon.svg';
static const String doctor_profile_reviews_icon = '$svgBasePath/doctor_profile_reviews_icon.svg';
static const String bookmark_icon = '$svgBasePath/bookmark_icon.svg';
static const String bookmark_filled_icon = '$svgBasePath/bookmark_filled_icon.svg';
static const String waiting_appointment_icon = '$svgBasePath/waitingAppo.svg';
static const String call_for_vitals = '$svgBasePath/call_for_vitals.svg';
static const String call_for_doctor = '$svgBasePath/call_for_doctor.svg';

@ -45,6 +45,8 @@ abstract class AuthenticationRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> insertPatientDeviceData({required dynamic patientDeviceDataRequest});
Future<Either<Failure, GenericApiModel<dynamic>>> getPatientDeviceData({required dynamic patientDeviceDataRequest});
Future<Either<Failure, GenericApiModel<dynamic>>> getPatientBloodType();
}
class AuthenticationRepoImp implements AuthenticationRepo {
@ -687,4 +689,37 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getPatientBloodType() async {
Map<String, dynamic> requestBody = {};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getPatientBloodTypeNew,
body: requestBody,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
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()));
}
}
}

@ -624,8 +624,10 @@ class AuthenticationViewModel extends ChangeNotifier {
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;
_appState.setAuthenticatedUser(activation.list!.first);
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
// _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
_appState.setUserBloodGroup = activation.patientBloodType ?? "N/A";
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
// Fetch patient blood type from new API
await getPatientBloodTypeNew();
}
// _appState.setUserBloodGroup = (activation.patientBlodType ?? "");
_appState.setAppAuthToken = activation.authenticationTokenId;
@ -1153,4 +1155,29 @@ class AuthenticationViewModel extends ChangeNotifier {
_navigationService.pushAndReplace(AppRoutes.landingScreen);
}
}
Future<void> getPatientBloodTypeNew() async {
try {
final result = await _authenticationRepo.getPatientBloodType();
result.fold(
(failure) async {
// Log error but don't show to user, keep existing blood type
log("Failed to fetch blood type: ${failure.message}");
},
(apiResponse) {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
// Extract blood type from response
String? bloodType = apiResponse.data['GetPatientBloodType'];
if (bloodType != null && bloodType.isNotEmpty) {
_appState.setUserBloodGroup = bloodType;
log("Blood type updated from new API: $bloodType");
}
}
},
);
} catch (e) {
log("Error calling getPatientBloodType: $e");
}
}
}

@ -107,6 +107,12 @@ abstract class BookAppointmentsRepo {
Function(String)? onError});
Future<Either<Failure, GenericApiModel<AppointmentNearestGateResponseModel>>> getAppointmentNearestGate({required int projectID, required int clinicID});
Future<Either<Failure, GenericApiModel<dynamic>>> isFavouriteDoctor(
{required int patientID, required int projectID, required int clinicID, required int doctorID, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> insertFavouriteDoctor(
{required int patientID, required int projectID, required int clinicID, required int doctorID, required bool isActive, Function(dynamic)? onSuccess, Function(String)? onError});
}
class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@ -1133,4 +1139,86 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> isFavouriteDoctor(
{required int patientID, required int projectID, required int clinicID, required int doctorID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapRequest = {"PatientID": patientID, "ProjectID": projectID, "ClinicID": clinicID, "DoctorID": doctorID};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
IS_FAVOURITE_DOCTOR,
body: mapRequest,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
if (onError != null) {
onError(error);
}
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response["IsFavouriteDoctor"],
);
if (onSuccess != null) {
onSuccess(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()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> insertFavouriteDoctor(
{required int patientID, required int projectID, required int clinicID, required int doctorID, required bool isActive, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapRequest = {"PatientID": patientID, "ProjectID": projectID, "ClinicID": clinicID, "DoctorID": doctorID, "IsActive": isActive};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
INSERT_FAVOURITE_DOCTOR,
body: mapRequest,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
if (onError != null) {
onError(error);
}
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
if (onSuccess != null) {
onSuccess(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()));
}
}
}

@ -98,6 +98,8 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isDoctorRatingDetailsLoading = false;
List<DoctorRateDetails> doctorDetailsList = [];
bool isFavouriteDoctor = false;
List<FreeSlot> slotsList = [];
List<TimeSlot> docFreeSlots = [];
List<TimeSlot> dayEvents = [];
@ -648,6 +650,15 @@ class BookAppointmentsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
doctorsProfileResponseModel = apiResponse.data!;
notifyListeners();
// Check if doctor is favorite after getting profile
checkIsFavouriteDoctor(
patientID: _appState.getAuthenticatedUser()!.patientId!,
projectID: doctorsProfileResponseModel.projectID ?? 0,
clinicID: doctorsProfileResponseModel.clinicID ?? 0,
doctorID: doctorsProfileResponseModel.doctorID ?? 0,
);
if (onSuccess != null) {
onSuccess(apiResponse);
}
@ -1530,4 +1541,79 @@ class BookAppointmentsViewModel extends ChangeNotifier {
},
);
}
void toggleFavouriteDoctor() {
isFavouriteDoctor = !isFavouriteDoctor;
notifyListeners();
}
void setIsFavouriteDoctor(bool value) {
isFavouriteDoctor = value;
notifyListeners();
}
Future<void> checkIsFavouriteDoctor({required int patientID, required int projectID, required int clinicID, required int doctorID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bookAppointmentsRepo.isFavouriteDoctor(
patientID: patientID,
projectID: projectID,
clinicID: clinicID,
doctorID: doctorID,
onSuccess: onSuccess,
onError: onError,
);
result.fold(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
if (onError != null) {
onError(apiResponse.errorMessage ?? "Failed to check favorite doctor");
}
} else if (apiResponse.messageStatus == 1) {
// Check the response for IsFavouriteDoctor flag
bool isFavorite = apiResponse.data;
setIsFavouriteDoctor(isFavorite);
if (onSuccess != null) {
onSuccess(apiResponse.data);
}
}
},
);
}
Future<void> insertFavouriteDoctor({required int patientID, required int projectID, required int clinicID, required int doctorID, required bool isActive, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bookAppointmentsRepo.insertFavouriteDoctor(
patientID: patientID,
projectID: projectID,
clinicID: clinicID,
doctorID: doctorID,
isActive: isActive,
onSuccess: onSuccess,
onError: onError,
);
result.fold(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
if (onError != null) {
onError(apiResponse.errorMessage ?? "Failed to update favorite doctor");
}
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data);
}
}
},
);
}
}

@ -0,0 +1,81 @@
import 'dart:convert';
class GetFavoriteDoctorsListModel {
int? id;
int? projectId;
int? clinicId;
int? doctorId;
int? patientId;
bool? patientOutSa;
bool? isActive;
String? createdOn;
dynamic modifiedOn;
String? doctorImageUrl;
String? doctorName;
String? doctorTitle;
String? nationalityFlagUrl;
String? nationalityId;
String? nationalityName;
List<String>? speciality;
GetFavoriteDoctorsListModel({
this.id,
this.projectId,
this.clinicId,
this.doctorId,
this.patientId,
this.patientOutSa,
this.isActive,
this.createdOn,
this.modifiedOn,
this.doctorImageUrl,
this.doctorName,
this.doctorTitle,
this.nationalityFlagUrl,
this.nationalityId,
this.nationalityName,
this.speciality,
});
factory GetFavoriteDoctorsListModel.fromRawJson(String str) => GetFavoriteDoctorsListModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GetFavoriteDoctorsListModel.fromJson(Map<String, dynamic> json) => GetFavoriteDoctorsListModel(
id: json["ID"],
projectId: json["ProjectID"],
clinicId: json["ClinicID"],
doctorId: json["DoctorID"],
patientId: json["PatientID"],
patientOutSa: json["PatientOutSA"],
isActive: json["IsActive"],
createdOn: json["CreatedOn"],
modifiedOn: json["ModifiedOn"],
doctorImageUrl: json["DoctorImageURL"],
doctorName: json["DoctorName"],
doctorTitle: json["DoctorTitle"],
nationalityFlagUrl: json["NationalityFlagURL"],
nationalityId: json["NationalityID"],
nationalityName: json["NationalityName"],
speciality: json["Speciality"] == null ? [] : List<String>.from(json["Speciality"]!.map((x) => x)),
);
Map<String, dynamic> toJson() => {
"ID": id,
"ProjectID": projectId,
"ClinicID": clinicId,
"DoctorID": doctorId,
"PatientID": patientId,
"PatientOutSA": patientOutSa,
"IsActive": isActive,
"CreatedOn": createdOn,
"ModifiedOn": modifiedOn,
"DoctorImageURL": doctorImageUrl,
"DoctorName": doctorName,
"DoctorTitle": doctorTitle,
"NationalityFlagURL": nationalityFlagUrl,
"NationalityID": nationalityId,
"NationalityName": nationalityName,
"Speciality": speciality == null ? [] : List<dynamic>.from(speciality!.map((x) => x)),
};
}

@ -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/book_appointments/models/resp_models/get_favorite_doctors_list.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';
@ -51,6 +52,8 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList();
Future<Either<Failure, GenericApiModel<List<GetFavoriteDoctorsListModel>>>> getFavouriteDoctorsList();
Future<Either<Failure, GenericApiModel<dynamic>>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel});
Future<Either<Failure, GenericApiModel<GetTamaraInstallmentsDetailsResponseModel>>> getTamaraInstallmentsDetails();
@ -531,6 +534,54 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetFavoriteDoctorsListModel>>>> getFavouriteDoctorsList() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<GetFavoriteDoctorsListModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_FAVOURITE_DOCTOR,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['Patient_GetFavouriteDoctorList'];
if (list == null || list.isEmpty) {
apiResponse = GenericApiModel<List<GetFavoriteDoctorsListModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: [],
);
return;
}
final appointmentsList = (list as List).map((item) => GetFavoriteDoctorsListModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetFavoriteDoctorsListModel>();
apiResponse = GenericApiModel<List<GetFavoriteDoctorsListModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentsList,
);
} 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()));
}
}
@override
Future<Either<Failure, GenericApiModel>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async {
Map<String, dynamic> requestBody = {

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.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';
@ -37,6 +38,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentPatientShareLoading = false;
bool isTimeLineAppointmentsLoading = false;
bool isPatientMyDoctorsLoading = false;
bool isPatientFavouriteDoctorsLoading = false;
bool isFavouriteDoctorsDataFetched = false;
bool isAppointmentDataToBeLoaded = true;
@ -64,6 +67,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
List<GetFavoriteDoctorsListModel> patientFavouriteDoctorsList = [];
List<PatientAppointmentHistoryResponseModel> patientEyeMeasurementsAppointmentsHistoryList = [];
// Grouping by Clinic and Hospital
@ -659,6 +664,51 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
Future<void> getPatientFavouriteDoctors({bool forceRefresh = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
// If data is already fetched and not forcing refresh, skip API call
if (isFavouriteDoctorsDataFetched && !forceRefresh) {
return;
}
isPatientFavouriteDoctorsLoading = true;
patientFavouriteDoctorsList.clear();
notifyListeners();
final result = await myAppointmentsRepo.getFavouriteDoctorsList();
result.fold(
(failure) async {
isPatientFavouriteDoctorsLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
isPatientFavouriteDoctorsLoading = false;
notifyListeners();
} else if (apiResponse.messageStatus == 1) {
patientFavouriteDoctorsList = apiResponse.data!;
isFavouriteDoctorsDataFetched = true;
isPatientFavouriteDoctorsLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
// Method to force refresh favorite doctors list
void refreshFavouriteDoctors() {
isFavouriteDoctorsDataFetched = false;
getPatientFavouriteDoctors(forceRefresh: true);
}
// Method to reset favorite doctors cache
void resetFavouriteDoctorsCache() {
isFavouriteDoctorsDataFetched = false;
}
Future<void> insertLiveCareVIDARequest(
{required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.insertLiveCareVIDARequest(clientRequestID: clientRequestID, patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel);

@ -906,6 +906,7 @@ abstract class LocaleKeys {
static const vat15 = 'vat15';
static const liveCare = 'liveCare';
static const recentVisits = 'recentVisits';
static const favouriteDoctors = 'favouriteDoctors';
static const searchByClinic = 'searchByClinic';
static const tapToSelectClinic = 'tapToSelectClinic';
static const searchByDoctor = 'searchByDoctor';

@ -63,12 +63,14 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
immediateLiveCareViewModel.initImmediateLiveCare();
if (appState.isAuthenticated) {
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors();
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (bookAppointmentsViewModel.selectedTabIndex == 1) {
if (appState.isAuthenticated) {
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors();
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
showUnKnownClinicBottomSheet();
}
} else {
@ -208,6 +210,136 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
);
}),
// Favorite Doctors Section
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// Show shimmer loading state
if (myAppointmentsVM.isPatientFavouriteDoctorsLoading) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
LocaleKeys.favouriteDoctors.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
SizedBox(
height: 110.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3, // Show 3 shimmer items
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemBuilder: (context, index) {
return SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 64.h,
height: 64.h,
decoration: BoxDecoration(
color: Colors.grey[300],
shape: BoxShape.circle,
),
).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
SizedBox(
width: 80.w,
child: Container(
height: 12.h,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4.r),
),
).toShimmer2(isShow: true),
),
],
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
),
],
);
}
// Show empty state or actual list
return myAppointmentsVM.patientFavouriteDoctorsList.isEmpty
? SizedBox()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
LocaleKeys.favouriteDoctors.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
SizedBox(
height: 110.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.patientFavouriteDoctorsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
myAppointmentsVM.patientFavouriteDoctorsList[index].doctorImageUrl!,
width: 64.h,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
SizedBox(
width: 80.w,
child: (myAppointmentsVM.patientFavouriteDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: myAppointmentsVM.patientFavouriteDoctorsList[index].clinicId,
projectID: myAppointmentsVM.patientFavouriteDoctorsList[index].projectId,
doctorID: myAppointmentsVM.patientFavouriteDoctorsList[index].doctorId,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
),
],
);
}),
],
],
);
@ -507,7 +639,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
}
},
onHospitalSearch: (value) {
data.searchHospitals(value ?? "");
data.searchHospitals(value);
},
selectedFacility: data.selectedFacility,
hmcCount: data.hmcCount,
@ -520,7 +652,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
// Navigator.of(context).pop();
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setLoadSpecificClinic(true);
bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first?.mainProjectID.toString());
bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital!.hospitalList.first!.mainProjectID.toString());
} else {
SizedBox.shrink();
}

@ -8,6 +8,7 @@ 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/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/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart';
@ -20,15 +21,12 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class DoctorProfilePage extends StatelessWidget {
DoctorProfilePage({super.key});
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
const DoctorProfilePage({super.key});
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
final bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
final appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -36,6 +34,41 @@ class DoctorProfilePage extends StatelessWidget {
Expanded(
child: CollapsingListView(
title: LocaleKeys.doctorProfile.tr(),
trailing: Consumer<BookAppointmentsViewModel>(
builder: (context, viewModel, child) {
return SizedBox(
width: 24.h,
height: 24.h,
child: Utils.buildSvgWithAssets(
icon: viewModel.isFavouriteDoctor ? AppAssets.bookmark_filled_icon : AppAssets.bookmark_icon,
width: 24.h,
height: 24.h,
iconColor: viewModel.isFavouriteDoctor ? AppColors.primaryRedColor : AppColors.textColor,
).onPress(() async {
viewModel.toggleFavouriteDoctor();
await viewModel.insertFavouriteDoctor(
patientID: appState.getAuthenticatedUser()!.patientId!,
projectID: viewModel.doctorsProfileResponseModel.projectID ?? 0,
clinicID: viewModel.doctorsProfileResponseModel.clinicID ?? 0,
doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0,
isActive: viewModel.isFavouriteDoctor,
onSuccess: (response) {
// Successfully added/removed favorite - refresh the favorites list
getIt.get<MyAppointmentsViewModel>().refreshFavouriteDoctors();
print(
viewModel.isFavouriteDoctor ? "Doctor added to favorites" : "Doctor removed from favorites",
);
},
onError: (error) {
// Revert the state on error
viewModel.toggleFavouriteDoctor();
Utils.showToast(error);
},
);
}),
);
},
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

Loading…
Cancel
Save