Merge branch 'master' into haroon_dev

pull/110/head
haroon amjad 1 month ago
commit ca47315b2c

@ -330,7 +330,7 @@ class Utils {
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h), SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr()) (noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText16(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true) .toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)
.paddingSymmetrical(64.w, 0.h), .paddingSymmetrical(64.w, 0.h),
SizedBox(height: 16.h), SizedBox(height: 16.h),
callToActionButton callToActionButton

@ -263,7 +263,7 @@ extension EmailValidator on String {
style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
); );
Widget toText20({Color? color, FontWeight? weight, bool isBold = false}) => Text( Widget toText20({Color? color, FontWeight? weight, bool isBold = false, }) => Text(
this, this,
style: TextStyle( style: TextStyle(
fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),

@ -101,10 +101,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
} }
@override @override
Future<Either<Failure, GenericApiModel<dynamic>>> checkPatientAuthentication({ Future<Either<Failure, GenericApiModel<dynamic>>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq, String? languageID}) async {
required dynamic checkPatientAuthenticationReq,
String? languageID,
}) async {
int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1; int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1;
checkPatientAuthenticationReq.patientOutSA = isOutKsa; checkPatientAuthenticationReq.patientOutSA = isOutKsa;
try { try {
@ -149,7 +146,6 @@ class AuthenticationRepoImp implements AuthenticationRepo {
sendActivationCodeReq.isDentalAllowedBackend = false; sendActivationCodeReq.isDentalAllowedBackend = false;
final payload = sendActivationCodeReq.toJson(); final payload = sendActivationCodeReq.toJson();
if (isFormFamilyFile) { if (isFormFamilyFile) {
payload.remove("MobileNo"); payload.remove("MobileNo");
payload.remove("NationalID"); payload.remove("NationalID");
payload.remove("SMSSignature"); payload.remove("SMSSignature");

@ -852,16 +852,13 @@ class AuthenticationViewModel extends ChangeNotifier {
request['isRegister'] = true; request['isRegister'] = true;
_appState.setAppAuthToken = response['LogInTokenID']; _appState.setAppAuthToken = response['LogInTokenID'];
if (isPatientOutsideSA(request: response)) { if (isPatientOutsideSA(request: response)) {
print("=======OUT SA=======");
sendActivationCode( sendActivationCode(
otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]),
nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), nationalIdOrFileNumber: request["PatientIdentificationID"].toString(),
phoneNumber: request["PatientMobileNumber"].toString(), phoneNumber: request["PatientMobileNumber"].toString(),
payload: request, payload: request,
isForRegister: true, isForRegister: true);
);
} else { } else {
print("=======IN SA=======");
chekUserNHICData(request: request); chekUserNHICData(request: request);
} }
} }

@ -95,7 +95,6 @@ class MedicalFileViewModel extends ChangeNotifier {
void onFamilyFileTabChange(int index) { void onFamilyFileTabChange(int index) {
setSelectedFamilyFileTabIndex = index; setSelectedFamilyFileTabIndex = index;
notifyListeners();
} }
setIsPatientVaccineListLoading(bool isLoading) { setIsPatientVaccineListLoading(bool isLoading) {
@ -474,9 +473,6 @@ class MedicalFileViewModel extends ChangeNotifier {
); );
} }
Future<void> addFamilyFile({required OTPTypeEnum otpTypeEnum}) async { Future<void> addFamilyFile({required OTPTypeEnum otpTypeEnum}) async {
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
AuthenticationViewModel authVM = getIt.get<AuthenticationViewModel>(); AuthenticationViewModel authVM = getIt.get<AuthenticationViewModel>();
@ -521,6 +517,13 @@ class MedicalFileViewModel extends ChangeNotifier {
}); });
} }
Future<void> clearAuthValues() async {
authVM.nationalIdController.clear();
authVM.phoneNumberController.clear();
authVM.emailController.clear();
authVM.dobController.clear();
}
Future<void> handleFamilyFileRequestOTPVerification() async { Future<void> handleFamilyFileRequestOTPVerification() async {
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
if (!_appState.getIsChildLoggedIn) { if (!_appState.getIsChildLoggedIn) {
@ -632,7 +635,7 @@ class MedicalFileViewModel extends ChangeNotifier {
getFamilyFiles(status: 0); getFamilyFiles(status: 0);
getAllPendingRecordsByResponseId(); getAllPendingRecordsByResponseId();
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
onFamilyFileTabChange(0); // onFamilyFileTabChange(0);
} }
}, },
); );

@ -88,10 +88,10 @@ class LoginScreenState extends State<LoginScreen> {
isAllowLeadingIcon: true, isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h),
leadingIcon: AppAssets.student_card, leadingIcon: AppAssets.student_card,
errorMessage: "Please enter a valid national ID or file number", errorMessage: "Please enter a valid national ID or file number".needTranslation,
hasError: false, hasError: false,
), ),
SizedBox(height: 16.h), // Adjusted to sizer unit (approx 16px) SizedBox(height: 16.h),
CustomButton( CustomButton(
height: 50.h, height: 50.h,
text: LocaleKeys.login.tr(), text: LocaleKeys.login.tr(),
@ -110,17 +110,12 @@ class LoginScreenState extends State<LoginScreen> {
} }
}, },
), ),
SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px) SizedBox(height: 10.h),
Center( Center(
child: RichText( child: RichText(
textAlign: TextAlign.center, textAlign: TextAlign.center,
text: TextSpan( text: TextSpan(
style: context.dynamicTextStyle( style: context.dynamicTextStyle(color: Colors.black, fontSize: 14.f, height: 26 / 16, fontWeight: FontWeight.w500),
color: Colors.black,
fontSize: 14.f, // Adjusted to sizer unit
height: 26 / 16, // This height is a ratio, may need re-evaluation
fontWeight: FontWeight.w500,
),
children: <TextSpan>[ children: <TextSpan>[
TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()), TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "), TextSpan(text: " "),
@ -140,9 +135,9 @@ class LoginScreenState extends State<LoginScreen> {
), ),
], ],
), ),
).withVerticalPadding(2), // Adjusted to sizer unit ).withVerticalPadding(2.h),
), ),
SizedBox(height: 20.h), // Adjusted to sizer unit (approx 14px) SizedBox(height: 20.h),
], ],
), ),
), ),

@ -78,8 +78,7 @@ class _RegisterNew extends State<RegisterNew> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.showLottie( Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.w, height: 200.h, fit: BoxFit.cover, repeat: true),
context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true),
SizedBox(height: 16.h), SizedBox(height: 16.h),
LocaleKeys.prepareToElevate.tr().toText32(isBold: true), LocaleKeys.prepareToElevate.tr().toText32(isBold: true),
SizedBox(height: 24.h), SizedBox(height: 24.h),
@ -113,7 +112,7 @@ class _RegisterNew extends State<RegisterNew> {
Divider(height: 1), Divider(height: 1),
TextInputWidget( TextInputWidget(
labelText: LocaleKeys.dob.tr(), labelText: LocaleKeys.dob.tr(),
hintText: "11 July, 1994", hintText: "11 July, 1994".needTranslation,
controller: authVm.dobController, controller: authVm.dobController,
focusNode: _dobFocusNode, focusNode: _dobFocusNode,
isEnable: true, isEnable: true,
@ -240,7 +239,7 @@ class _RegisterNew extends State<RegisterNew> {
), ),
), ),
), ),
SizedBox(height: 30), SizedBox(height: 30.h),
], ],
), ),
), ),

@ -69,7 +69,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
height: double.infinity, height: double.infinity,
child: SingleChildScrollView( child: SingleChildScrollView(
reverse: false, reverse: false,
padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 0.h), padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 0.h),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@ -98,7 +98,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.user_circle, leadingIcon: AppAssets.user_circle,
labelColor: AppColors.textColor, labelColor: AppColors.textColor,
).paddingSymmetrical(0.h, 16.h), ).paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor), Divider(height: 1.h, color: AppColors.greyColor),
TextInputWidget( TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(), labelText: LocaleKeys.nationalIdNumber.tr(),
hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""), hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""),
@ -130,7 +130,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true, hasSelectionCustomIcon: true,
isAllowRadius: false, isAllowRadius: false,
labelColor: AppColors.textColor, labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down, selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.user_full, leadingIcon: AppAssets.user_full,
).withVerticalPadding(8); ).withVerticalPadding(8);
@ -167,7 +167,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true, hasSelectionCustomIcon: true,
isAllowRadius: false, isAllowRadius: false,
labelColor: AppColors.textColor, labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down, selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.smart_phone, leadingIcon: AppAssets.smart_phone,
).withVerticalPadding(8); ).withVerticalPadding(8);
@ -188,7 +188,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.smart_phone, leadingIcon: AppAssets.smart_phone,
onChange: (value) {}) onChange: (value) {})
.paddingSymmetrical(0.h, 16.h), .paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor), Divider(height: 1.h, color: AppColors.greyColor),
authVM!.isUserFromUAE() authVM!.isUserFromUAE()
? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( ? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>(
selector: (context, authViewModel) { selector: (context, authViewModel) {
@ -217,10 +217,10 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true, hasSelectionCustomIcon: true,
labelColor: AppColors.textColor, labelColor: AppColors.textColor,
isAllowRadius: false, isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down, selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.globe, leadingIcon: AppAssets.globe,
).withVerticalPadding(8); ).withVerticalPadding(8.h);
}, },
) )
: TextInputWidget( : TextInputWidget(
@ -256,7 +256,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.call) leadingIcon: AppAssets.call)
.paddingSymmetrical(0.h, 16.h), .paddingSymmetrical(0.h, 16.h),
Divider( Divider(
height: 1, height: 1.h,
color: AppColors.greyColor, color: AppColors.greyColor,
), ),
TextInputWidget( TextInputWidget(
@ -292,9 +292,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
iconColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor,
), ),
), ),
SizedBox( SizedBox(width: 16.w),
width: 16,
),
Expanded( Expanded(
child: CustomButton( child: CustomButton(
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor,

@ -27,7 +27,6 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
@ -55,6 +54,7 @@ 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/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.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/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@ -86,6 +86,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
appState = getIt.get<AppState>(); appState = getIt.get<AppState>();
scheduleMicrotask(() { scheduleMicrotask(() {
if (appState.isAuthenticated) { if (appState.isAuthenticated) {
labViewModel.initLabProvider();
insuranceViewModel.initInsuranceProvider(); insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList(); medicalFileViewModel.getPatientSickLeaveList();
@ -149,20 +150,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: "Type any record".needTranslation,
controller: TextEditingController(),
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
leadingIcon: AppAssets.student_card,
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Container( Container(
width: double.infinity, width: double.infinity,
@ -226,9 +213,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
), ),
AppCustomChipWidget( AppCustomChipWidget(
icon: AppAssets.blood_icon, icon: AppAssets.blood_icon,
labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}".needTranslation, labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}",
iconColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor,
labelPadding: EdgeInsetsDirectional.only(end: 4.w, start: 0.w), labelPadding: EdgeInsetsDirectional.only(end: 8.w),
), ),
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) { Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return AppCustomChipWidget( return AppCustomChipWidget(
@ -237,8 +224,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 12.w, iconSize: 12.w,
backgroundColor: backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
labelPadding: EdgeInsetsDirectional.only(end: 8.w), labelPadding: EdgeInsetsDirectional.only(end: 8.w),
); );
}), }),
@ -249,27 +235,112 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
), ),
).paddingSymmetrical(24.w, 0.0), ).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) { TextInputWidget(
return Column( labelText: LocaleKeys.search.tr(context: context),
children: [ hintText: "Type any record".needTranslation,
CustomTabBar( controller: TextEditingController(),
activeTextColor: AppColors.primaryRedColor, keyboardType: TextInputType.number,
activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), isEnable: true,
tabs: [ prefix: null,
CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), autoFocus: false,
CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), isBorderAllowed: false,
CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), isAllowLeadingIcon: true,
CustomTabBarModel(AppAssets.more, "More".needTranslation), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
], leadingIcon: AppAssets.search_icon,
onTabChange: (index) { hintColor: AppColors.textColor,
medicalFileVM.onTabChanged(index); ).paddingSymmetrical(24.w, 0.0),
}, SizedBox(height: 16.h),
).paddingSymmetrical(24.w, 0.0), // Using CustomExpandableList
SizedBox(height: 24.h), CustomExpandableList(
getSelectedTabData(medicalFileVM.selectedTabIndex), expansionMode: ExpansionMode.exactlyOne,
], dividerColor: Color(0xFF2B353E1A),
); itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
}), items: [
ExpandableListItem(
title: "Medical Services".toText18(weight: FontWeight.w600),
children: [
SizedBox(height: 10.h),
getSelectedTabData(0),
],
),
ExpandableListItem(
title: "Medical Reports".toText18(
weight: FontWeight.w600,
),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
getSelectedTabData(2),
]),
ExpandableListItem(
title: "Insurance & Payments".toText18(weight: FontWeight.w600),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
getSelectedTabData(1),
],
),
ExpandableListItem(
title: "Tracker & Others".toText18(weight: FontWeight.w600),
expandedBackgroundColor: Colors.transparent,
children: [
Text("Blood Report"),
SizedBox(height: 8),
Text("X-Ray Report"),
],
),
],
theme: ExpandableListTheme.custom(
defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor),
),
).paddingSymmetrical(16.w, 0.0),
// ExpansionTileList(
// shrinkWrap: true,
// trailing: Icon(Icons.arrow_drop_down),
// expansionMode: ExpansionMode.exactlyOne,
// children: <ExpansionTile>[
// ExpansionTile(
// title: "Medical Services".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// iconColor: Color(0xFF2E3039),
// trailing: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w),
// children: <Widget>[Text('Child 1')],
// ),
// ExpansionTile(
// title: "Medical Reports".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 2')],
// ),
// ExpansionTile(
// title: "Insurance & Payments".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 3')],
// ),
// ExpansionTile(
// title: "Tracker & Others".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 4')],
// ),
// ],
// ),
// Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) {
// return Column(
// children: [
// CustomTabBar(
// activeTextColor: AppColors.primaryRedColor,
// activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1),
// tabs: [
// CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation),
// CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)),
// CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation),
// CustomTabBarModel(AppAssets.more, "More".needTranslation),
// ],
// onTabChange: (index) {
// medicalFileVM.onTabChanged(index);
// },
// ).paddingSymmetrical(24.w, 0.0),
// SizedBox(height: 24.h),
// getSelectedTabData(medicalFileVM.selectedTabIndex),
// ],
// );
// }),
], ],
), ),
); );
@ -325,7 +396,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"Appointments & visits".needTranslation.toText18(isBold: true), "Appointments & visits".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
Row( Row(
children: [ children: [
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
@ -334,7 +405,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
], ],
), ),
], ],
).paddingSymmetrical(24.w, 0.h).onPress(() { ).paddingSymmetrical(0.w, 0.h).onPress(() {
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: MyAppointmentsPage(), page: MyAppointmentsPage(),
@ -357,8 +428,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? Container( ? Container(
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
decoration: decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true),
child: Column( child: Column(
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
@ -391,7 +461,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
: ListView.separated( : ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length, itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
@ -409,45 +478,18 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
onRescheduleTap: () { onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
}, },
onAskDoctorTap: () async { onAskDoctorTap: () {},
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
},
)), )),
), ),
), ),
); );
}, },
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
), ),
); ).paddingSymmetrical(0.w, 0.h);
}), }),
SizedBox(height: 24.h), SizedBox(height: 10.h),
"Lab & Radiology".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), "Lab & Radiology".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Row( Row(
children: [ children: [
@ -459,7 +501,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests,
isLoading: labViewModel.isLabOrdersLoading, isLoading: labViewModel.isLabOrdersLoading,
).onPress(() { ).onPress(() {
labViewModel.initLabProvider();
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: LabOrdersPage(), page: LabOrdersPage(),
@ -467,11 +508,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
); );
}), }),
), ),
SizedBox(width: 16.h), SizedBox(width: 8.w),
Expanded( Expanded(
child: LabRadCard( child: LabRadCard(
icon: AppAssets.radiology_icon, icon: AppAssets.radiology_icon,
labelText: LocaleKeys.radiology.tr(context: context), labelText: "${LocaleKeys.radiology.tr(context: context)} Results".needTranslation,
// labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar",
// labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"],
labOrderTests: [], labOrderTests: [],
isLoading: false, isLoading: false,
@ -484,13 +526,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}), }),
), ),
], ],
).paddingSymmetrical(24.w, 0.h), ).paddingSymmetrical(0.w, 0.h),
SizedBox(height: 24.h), SizedBox(height: 24.h),
"Active Medications & Prescriptions".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), "Active Medications & Prescriptions".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) { Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
return prescriptionVM.isPrescriptionsOrdersLoading return prescriptionVM.isPrescriptionsOrdersLoading
? const CommonShimmerWidget().paddingSymmetrical(24.w, 0.h) ? const CommonShimmerWidget().paddingSymmetrical(0.w, 0.h)
: prescriptionVM.patientPrescriptionOrders.isNotEmpty : prescriptionVM.patientPrescriptionOrders.isNotEmpty
? Container( ? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -533,13 +575,11 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
spacing: 3.w, spacing: 3.w,
runSpacing: 4.w, runSpacing: 4.w,
children: [ children: [
AppCustomChipWidget( AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget( AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon, icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate( labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate( DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false, false,
), ),
), ),
@ -552,19 +592,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Transform.flip( Transform.flip(
flipX: appState.isArabic(), flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets( child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
width: 15.w,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor)),
], ],
).onPress(() { ).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading(); prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: PrescriptionDetailPage( page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
), ),
); );
}), }),
@ -574,9 +608,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}, },
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
), ),
SizedBox(height: 8.h), SizedBox(height: 24.h),
const Divider(color: AppColors.dividerColor), const Divider(color: AppColors.dividerColor),
SizedBox(height: 8.h), SizedBox(height: 24.h),
Row( Row(
children: [ children: [
Expanded( Expanded(
@ -637,14 +671,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w, width: 62.w,
height: 62.h, height: 62.h,
), ),
).paddingSymmetrical(24.w, 0.h); ).paddingSymmetrical(0.w, 0.h);
}), }),
SizedBox(height: 24.h), SizedBox(height: 24.h),
//My Doctor Section //My Doctor Section
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.myDoctor.tr(context: context).toText18(isBold: true), LocaleKeys.myDoctor.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2),
Row( Row(
children: [ children: [
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
@ -660,7 +694,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
); );
}), }),
], ],
).paddingSymmetrical(24.w, 0.h), ).paddingSymmetrical(0.w, 0.h),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) { Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return myAppointmentsVM.isPatientMyDoctorsLoading return myAppointmentsVM.isPatientMyDoctorsLoading
@ -674,10 +708,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fit: BoxFit.cover, fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r), ).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h), SizedBox(height: 8.h),
("Dr. John Smith Smith Smith") ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: true),
], ],
) )
: myAppointmentsVM.patientMyDoctorsList.isEmpty : myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -695,14 +726,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w, width: 62.w,
height: 62.h, height: 62.h,
), ),
).paddingSymmetrical(24.w, 0.h) ).paddingSymmetrical(0.w, 0.h)
: SizedBox( : SizedBox(
height: 110.h, height: 100.h,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.patientMyDoctorsList.length, itemCount: myAppointmentsVM.patientMyDoctorsList.length,
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
position: index, position: index,
@ -722,8 +752,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fit: BoxFit.cover, fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r), ).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h), SizedBox(height: 8.h),
SizedBox( Expanded(
width: 80.w,
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString() .toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
@ -762,10 +791,10 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}, },
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
), ),
); ).paddingSymmetrical(0.w, 0);
}), }),
SizedBox(height: 24.h), SizedBox(height: 24.h),
"Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), "Others".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h), SizedBox(height: 16.h),
GridView( GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
@ -825,7 +854,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
); );
}), }),
], ],
).paddingSymmetrical(24.w, 0.0), ).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
); );
@ -840,7 +869,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labOrder: null, labOrder: null,
index: index, index: index,
isLoading: true, isLoading: true,
).paddingSymmetrical(24.w, 0.0) ).paddingSymmetrical(0.w, 0.0)
: insuranceVM.patientInsuranceList.isNotEmpty : insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard( ? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
@ -867,14 +896,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () { onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
}, },
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0), borderColor: AppColors.bgGreenColor.withOpacity(0.0),
@ -886,7 +910,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
height: isFoldable ? 50.h : 40.h, height: isFoldable ? 50.h : 40.h,
).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h), ).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h),
), ),
).paddingSymmetrical(24.w, 0.h); ).paddingSymmetrical(0.w, 0.h);
}), }),
SizedBox(height: 10.h), SizedBox(height: 10.h),
GridView( GridView(
@ -941,7 +965,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconSize: 36.w, iconSize: 36.w,
), ),
], ],
).paddingSymmetrical(24.w, 0.0), ).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h), SizedBox(height: 16.h),
], ],
); );
@ -954,12 +978,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? PatientSickLeaveCard( ? PatientSickLeaveCard(
patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), patientSickLeavesResponseModel: PatientSickLeavesResponseModel(),
isLoading: true, isLoading: true,
).paddingSymmetrical(24.w, 0.0) ).paddingSymmetrical(0.w, 0.0)
: medicalFileVM.patientSickLeaveList.isNotEmpty : medicalFileVM.patientSickLeaveList.isNotEmpty
? PatientSickLeaveCard( ? PatientSickLeaveCard(
patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first,
isLoading: false, isLoading: false,
).paddingSymmetrical(24.w, 0.0) ).paddingSymmetrical(0.w, 0.0)
: Container( : Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, color: AppColors.whiteColor,
@ -973,7 +997,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w, width: 62.w,
height: 62.h, height: 62.h,
), ),
).paddingSymmetrical(24.w, 0.h); ).paddingSymmetrical(0.w, 0.h);
}), }),
SizedBox(height: 16.h), SizedBox(height: 16.h),
GridView( GridView(
@ -1026,7 +1050,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
); );
}), }),
], ],
).paddingSymmetrical(24.w, 0.0), ).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
); );

@ -27,20 +27,27 @@ class LabRadCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 18.r, hasShadow: false),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Utils.buildSvgWithAssets( Utils.buildSvgWithAssets(
icon: icon, icon: icon,
width: 40.w, width: 40.w,
height: 40.h, height: 40.h,
fit: BoxFit.contain, fit: BoxFit.cover,
).toShimmer2(isShow: false, radius: 12.r), ).toShimmer2(isShow: false, radius: 12.r),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Flexible(child: labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.r, height: 32.h)), Flexible(
child: labelText.toText12(isBold: true, maxLine: 2),
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 10.w, height: 10.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
),
], ],
), ),
// SizedBox(height: 16.h), // SizedBox(height: 16.h),
@ -61,21 +68,21 @@ class LabRadCard extends StatelessWidget {
// ) // )
// : "You don't have any records yet".needTranslation.toText13( // : "You don't have any records yet".needTranslation.toText13(
// color: AppColors.greyTextColor, isCenter: true), // color: AppColors.greyTextColor, isCenter: true),
SizedBox(height: 16.h), // SizedBox(height: 16.h),
Row( // Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ // children: [
"View All".toText12(isBold: true), // SizedBox.shrink(),
Transform.flip( // Transform.flip(
flipX: appState.isArabic(), // flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets( // child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor) // icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)
.toShimmer2(isShow: false, radius: 12.r), // .toShimmer2(isShow: false, radius: 12.r),
), // ),
], // ],
) // )
], ],
).paddingAll(16.w), ).paddingAll(12.w),
); );
} }
} }

@ -46,6 +46,9 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
void initState() { void initState() {
super.initState(); super.initState();
medicalVM = context.read<MedicalFileViewModel>(); medicalVM = context.read<MedicalFileViewModel>();
WidgetsBinding.instance.addPostFrameCallback((_) {
medicalVM?.onFamilyFileTabChange(0);
});
} }
@override @override
@ -65,6 +68,7 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
text: "Add a new family member".needTranslation, text: "Add a new family member".needTranslation,
onPressed: () { onPressed: () {
DialogService dialogService = getIt.get<DialogService>(); DialogService dialogService = getIt.get<DialogService>();
medicalVM!.clearAuthValues();
dialogService.showAddFamilyFileSheet( dialogService.showAddFamilyFileSheet(
label: "Add Family Member".needTranslation, label: "Add Family Member".needTranslation,
message: "Please fill the below field to add a new family member to your profile".needTranslation, message: "Please fill the below field to add a new family member to your profile".needTranslation,
@ -83,13 +87,17 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
children: [ children: [
appState.isChildLoggedIn appState.isChildLoggedIn
? SizedBox() ? SizedBox()
: CustomTabBar( : Selector<MedicalFileViewModel, int>(
activeBackgroundColor: AppColors.secondaryLightRedColor, selector: (_, model) => model.getSelectedFamilyFileTabIndex,
activeTextColor: AppColors.primaryRedColor, builder: (context, selectedIndex, child) => CustomTabBar(
tabs: [CustomTabBarModel(null, LocaleKeys.family.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())], activeBackgroundColor: AppColors.secondaryLightRedColor,
onTabChange: (index) { activeTextColor: AppColors.primaryRedColor,
medicalVM!.onFamilyFileTabChange(index); // selectedIndex: selectedIndex,
}, tabs: [CustomTabBarModel(null, LocaleKeys.family.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())],
onTabChange: (index) {
medicalVM!.onFamilyFileTabChange(index);
},
),
), ),
appState.isChildLoggedIn ? SizedBox() : SizedBox(height: 25.h), appState.isChildLoggedIn ? SizedBox() : SizedBox(height: 25.h),
Selector<MedicalFileViewModel, int>(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)), Selector<MedicalFileViewModel, int>(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)),
@ -117,7 +125,7 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
case 1: case 1:
return FamilyCards( return FamilyCards(
profiles: medicalVM!.pendingFamilyFiles, profiles: medicalVM!.pendingFamilyFiles,
isRequestDesign: true, isRequestDesign: medicalVM!.getSelectedFamilyFileTabIndex == 1,
onSelect: (FamilyFileResponseModelLists profile) { onSelect: (FamilyFileResponseModelLists profile) {
medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3); medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3);
}, },

@ -129,13 +129,21 @@ class DialogServiceImp implements DialogService {
if (context == null) return; if (context == null) return;
showCommonBottomSheetWithoutHeight(context, showCommonBottomSheetWithoutHeight(context,
title: label ?? "", title: label ?? "",
child: FamilyCards( child: Column(
profiles: profiles, crossAxisAlignment: CrossAxisAlignment.start,
onSelect: (FamilyFileResponseModelLists profile) { mainAxisAlignment: MainAxisAlignment.start,
onSwitchPress(profile); children: [
}, (message).toText16(isBold: false, color: AppColors.textColor),
onRemove: (FamilyFileResponseModelLists profile) {}, SizedBox(height: 24.h),
isShowDetails: false, FamilyCards(
profiles: profiles,
onSelect: (FamilyFileResponseModelLists profile) {
onSwitchPress(profile);
},
onRemove: (FamilyFileResponseModelLists profile) {},
isShowDetails: false,
),
],
), ),
callBackFunc: () {}); callBackFunc: () {});
} }

@ -10,7 +10,7 @@ class ExpandableBottomSheet extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print("the currently selected item is ${bottomSheetType}"); print("the currently selected item is $bottomSheetType");
return AnimatedCrossFade( return AnimatedCrossFade(
duration: const Duration(milliseconds: 600), duration: const Duration(milliseconds: 600),
firstChild:children[BottomSheetType.FIXED] ?? SizedBox.shrink(), firstChild:children[BottomSheetType.FIXED] ?? SizedBox.shrink(),

@ -0,0 +1,301 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
// ==================== MAIN CUSTOM WIDGET ====================
class CustomExpandableList extends StatefulWidget {
final List<ExpandableListItem> items;
final ExpansionMode expansionMode;
final EdgeInsetsGeometry? itemPadding;
final EdgeInsetsGeometry? contentPadding;
final Color? dividerColor;
final double dividerHeight;
final bool showDividers;
final Duration animationDuration;
final Curve animationCurve;
final ExpandableListTheme? theme;
final Function(int index, bool isExpanded)? onItemToggled;
const CustomExpandableList({
Key? key,
required this.items,
this.expansionMode = ExpansionMode.multiple,
this.itemPadding,
this.contentPadding,
this.dividerColor,
this.dividerHeight = 1.0,
this.showDividers = true,
this.animationDuration = const Duration(milliseconds: 300),
this.animationCurve = Curves.easeInOut,
this.theme,
this.onItemToggled,
}) : super(key: key);
@override
_CustomExpandableListState createState() => _CustomExpandableListState();
}
class _CustomExpandableListState extends State<CustomExpandableList> {
late List<bool> _expandedStates;
@override
void initState() {
super.initState();
_expandedStates = List<bool>.generate(
widget.items.length,
(index) => widget.items[index].initiallyExpanded,
);
}
void _toggleItem(int index) {
setState(() {
if (widget.expansionMode == ExpansionMode.exactlyOne) {
// Close all others, open this one
for (int i = 0; i < _expandedStates.length; i++) {
_expandedStates[i] = (i == index) ? !_expandedStates[i] : false;
}
} else if (widget.expansionMode == ExpansionMode.atMostOne) {
// Toggle this one, close if opening another
bool wasExpanded = _expandedStates[index];
for (int i = 0; i < _expandedStates.length; i++) {
if (i == index) {
_expandedStates[i] = !wasExpanded;
} else if (!wasExpanded) {
_expandedStates[i] = false;
}
}
} else {
// Multiple - just toggle the clicked item
_expandedStates[index] = !_expandedStates[index];
}
});
widget.onItemToggled?.call(index, _expandedStates[index]);
}
@override
Widget build(BuildContext context) {
final theme = widget.theme ?? ExpandableListTheme.defaultTheme(context);
return Column(
children: List.generate(widget.items.length, (index) {
final item = widget.items[index];
final isExpanded = _expandedStates[index];
final isLast = index == widget.items.length - 1;
return Column(
children: [
_ExpandableListItemWidget(
title: item.title,
isExpanded: isExpanded,
onTap: () => _toggleItem(index),
padding: widget.itemPadding,
contentPadding: widget.contentPadding,
theme: theme,
leading: item.leading,
trailing: item.trailing,
backgroundColor: item.backgroundColor,
expandedBackgroundColor: item.expandedBackgroundColor,
animationDuration: widget.animationDuration,
animationCurve: widget.animationCurve,
children: item.children,
),
if (widget.showDividers && !isLast)
Divider(
height: widget.dividerHeight,
color: widget.dividerColor ?? theme.dividerColor,
).paddingSymmetrical(12.h,0),
],
);
}),
);
}
}
// ==================== SUPPORTING WIDGETS ====================
class _ExpandableListItemWidget extends StatelessWidget {
final Widget title;
final List<Widget> children;
final bool isExpanded;
final VoidCallback onTap;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? contentPadding;
final ExpandableListTheme theme;
final Widget? leading;
final Widget? trailing;
final Color? backgroundColor;
final Color? expandedBackgroundColor;
final Duration animationDuration;
final Curve animationCurve;
const _ExpandableListItemWidget({
super.key,
required this.title,
required this.children,
required this.isExpanded,
required this.onTap,
this.padding,
this.contentPadding,
required this.theme,
this.leading,
this.trailing,
this.backgroundColor,
this.expandedBackgroundColor,
required this.animationDuration,
required this.animationCurve,
});
@override
Widget build(BuildContext context) {
return Container(
color: isExpanded
? (expandedBackgroundColor ?? theme.expandedBackgroundColor)
: (backgroundColor ?? theme.backgroundColor),
child: Column(
children: [
// Header
InkWell(
onTap: onTap,
child: Padding(
padding: padding ?? theme.itemPadding,
child: Row(
children: [
if (leading != null) ...[
leading!,
SizedBox(width: theme.leadingSpacing),
],
Expanded(child: title),
if (trailing != null)
trailing!
else
AnimatedRotation(
turns: isExpanded ? 0.5 : 0.0,
duration: animationDuration,
curve: animationCurve,
child: theme.defaultTrailingIcon,
),
],
),
),
),
// Content
AnimatedSize(
duration: animationDuration,
curve: animationCurve,
child: Container(
constraints: BoxConstraints(
minHeight: isExpanded ? 0.0 : 0.0,
maxHeight: isExpanded ? double.infinity : 0.0,
),
child: isExpanded
? Padding(
padding: contentPadding ?? theme.contentPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
)
: SizedBox.shrink(),
),
),
],
),
);
}
}
// ==================== DATA MODELS ====================
class ExpandableListItem {
final Widget title;
final List<Widget> children;
final Widget? leading;
final Widget? trailing;
final bool initiallyExpanded;
final Color? backgroundColor;
final Color? expandedBackgroundColor;
const ExpandableListItem({
required this.title,
required this.children,
this.leading,
this.trailing,
this.initiallyExpanded = false,
this.backgroundColor,
this.expandedBackgroundColor,
});
}
// ==================== THEME ====================
class ExpandableListTheme {
final Color backgroundColor;
final Color expandedBackgroundColor;
final Color dividerColor;
final Widget defaultTrailingIcon;
final EdgeInsetsGeometry itemPadding;
final EdgeInsetsGeometry contentPadding;
final double leadingSpacing;
const ExpandableListTheme({
required this.backgroundColor,
required this.expandedBackgroundColor,
required this.dividerColor,
required this.defaultTrailingIcon,
required this.itemPadding,
required this.contentPadding,
this.leadingSpacing = 12.0,
});
factory ExpandableListTheme.defaultTheme(BuildContext context) {
final textColor = Theme.of(context).textTheme.bodyLarge?.color ?? Colors.black;
return ExpandableListTheme(
backgroundColor: Colors.transparent,
expandedBackgroundColor: Colors.transparent,
dividerColor: Colors.grey.shade300,
defaultTrailingIcon: Icon(
Icons.keyboard_arrow_down,
color: textColor,
size: 24,
),
itemPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
contentPadding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0),
);
}
factory ExpandableListTheme.custom({
Color? backgroundColor,
Color? expandedBackgroundColor,
Color? dividerColor,
Widget? defaultTrailingIcon,
EdgeInsetsGeometry? itemPadding,
EdgeInsetsGeometry? contentPadding,
double? leadingSpacing,
}) {
return ExpandableListTheme(
backgroundColor: backgroundColor ?? Colors.transparent,
expandedBackgroundColor: expandedBackgroundColor ?? Colors.grey.shade50,
dividerColor: dividerColor ?? Colors.grey.shade300,
defaultTrailingIcon: defaultTrailingIcon ??
Icon(Icons.keyboard_arrow_down, color: Colors.black, size: 24),
itemPadding: itemPadding ?? const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
contentPadding: contentPadding ?? const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0),
leadingSpacing: leadingSpacing ?? 12.0,
);
}
}
// ==================== ENUMS ====================
enum ExpansionMode {
multiple, // Multiple items can be expanded
exactlyOne, // Exactly one item expanded at a time
atMostOne, // Zero or one item expanded at a time
}

@ -47,38 +47,40 @@ class TextInputWidget extends StatelessWidget {
final bool isMultiline; final bool isMultiline;
final int minLines; final int minLines;
final int maxLines; final int maxLines;
final Color? hintColor;
// final List<Country> countryList; // final List<Country> countryList;
// final Function(Country)? onCountryChange; // final Function(Country)? onCountryChange;
TextInputWidget( TextInputWidget({
{super.key, super.key,
required this.labelText, required this.labelText,
required this.hintText, required this.hintText,
this.controller, this.controller,
this.onChange, this.onChange,
this.onCalendarTypeChanged, this.onCalendarTypeChanged,
this.prefix, this.prefix,
this.isEnable = true, this.isEnable = true,
this.isBorderAllowed = true, this.isBorderAllowed = true,
this.isAllowRadius = true, this.isAllowRadius = true,
this.isReadOnly = false, this.isReadOnly = false,
this.keyboardType = TextInputType.number, this.keyboardType = TextInputType.number,
this.focusNode, this.focusNode,
this.autoFocus = false, this.autoFocus = false,
this.padding, this.padding,
this.isAllowLeadingIcon = false, this.isAllowLeadingIcon = false,
this.leadingIcon, this.leadingIcon,
this.isCountryDropDown = false, this.isCountryDropDown = false,
this.hasError = false, this.hasError = false,
this.errorMessage, this.errorMessage,
this.onCountryChange, this.onCountryChange,
this.selectionType, this.selectionType,
this.fontSize, this.fontSize,
this.isWalletAmountInput = false, this.isWalletAmountInput = false,
this.suffix, this.suffix,
this.labelColor, this.labelColor,
this.onSubmitted, this.onSubmitted,
this.hintColor,
// multiline defaults // multiline defaults
this.isMultiline = false, this.isMultiline = false,
this.minLines = 3, this.minLines = 3,
@ -274,7 +276,7 @@ class TextInputWidget extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
hintText: hintText, hintText: hintText,
hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.75), hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75),
prefixIconConstraints: BoxConstraints(minWidth: 30.h), prefixIconConstraints: BoxConstraints(minWidth: 30.h),
prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,

@ -11,14 +11,13 @@ class CustomRadioOption<T> extends StatelessWidget {
// final Widget child; // The content of your radio option (e.g., Text, Image) // final Widget child; // The content of your radio option (e.g., Text, Image)
const CustomRadioOption({ const CustomRadioOption(
super.key, {super.key,
required this.value, required this.value,
required this.groupValue, required this.groupValue,
required this.onChanged, required this.onChanged,
// required this.child, // required this.child,
required this.text, required this.text});
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -31,13 +30,12 @@ class CustomRadioOption<T> extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 20.h, width: 18.h,
height: 20.h, height: 18.h,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor,
border: Border.all(color: isSelected ? AppColors.primaryRedColor : AppColors.bottomNAVBorder, width: 2.h), border: Border.all(color: isSelected ? AppColors.primaryRedColor : AppColors.bottomNAVBorder, width: 2.h)),
),
), ),
SizedBox(width: 8.h), SizedBox(width: 8.h),
text.toText16(weight: FontWeight.w500), // The provided content text.toText16(weight: FontWeight.w500), // The provided content

Loading…
Cancel
Save