Merge branch 'master' into dev_aamir

# Conflicts:
#	lib/presentation/medical_file/medical_file_page.dart
dev_aamir
aamir-csol 1 week ago
commit 3e5c3a3a27

@ -1,16 +1,166 @@
# hmg_patient_app_new
# HMG Patient App
New HMG Patient App
A comprehensive Flutter-based mobile application for HMG (Hospital Management Group) patients,
providing seamless healthcare services and patient management features.
## Getting Started
## 📱 Features
This project is a starting point for a Flutter application.
- **Patient Registration & Authentication**: Secure login and registration system
- **Appointment Management**: Book, reschedule, and manage medical appointments
- **Medical Records**: Access to personal health records and medical history
- **Doctor Consultation**: Video consultations and chat with healthcare providers
- **Health Monitoring**: Track vital signs and health metrics
- **Prescription Management**: View and manage prescriptions
- **Payment Integration**: Secure payment processing for medical services
- **Multi-language Support**: Available in English and Arabic
- **Push Notifications**: Real-time updates for appointments and health reminders
- **Calendar Integration**: Sync appointments with device calendar
- **Location Services**: Find nearby hospitals and clinics
A few resources to get you started if this is your first Flutter project:
## 🚀 Getting Started
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
### Prerequisites
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
- Flutter SDK (>=3.0.0)
- Dart SDK (>=3.0.0)
- Android Studio / VS Code
- Android SDK (for Android development)
- Xcode (for iOS development, macOS only)
### Installation
1. **Clone the repository:**
```bash
git clone <repository-url>
cd HMG_Patient_App_New
```
2. **Install dependencies:**
```bash
flutter pub get
```
3. **Configure Firebase:**
- Add your `google-services.json` file to `android/app/`
- Add your `GoogleService-Info.plist` file to `ios/Runner/`
4. **Run the application:**
```bash
flutter run
```
## 🏗️ Project Structure
```
lib/
├── core/ # Core utilities and constants
├── extensions/ # Dart extensions
├── features/ # Feature-based modules
├── generated/ # Generated files (localization, etc.)
├── presentation/ # UI screens and widgets
├── routes/ # App navigation and routing
├── services/ # Business logic and API services
├── theme/ # App theming and styling
├── widgets/ # Reusable UI components
├── main.dart # App entry point
└── splashPage.dart # Splash screen
assets/
├── animations/ # Lottie animations
├── fonts/ # Custom fonts (Poppins, Gess Two)
├── images/ # PNG and SVG images
├── json/ # JSON data files
└── langs/ # Localization files
```
## 🛠️ Technologies Used
- **Framework**: Flutter
- **Language**: Dart
- **State Management**: [Your state management solution]
- **Backend Services**: Firebase
- **Authentication**: Firebase Auth
- **Database**: Cloud Firestore
- **Push Notifications**: Firebase Cloud Messaging
- **Maps**: Google Maps
- **Payment**: Amazon PayFort
- **Video Calling**: Flutter Zoom Video SDK
- **Local Storage**: SQLite, Shared Preferences
## 📱 Supported Platforms
- ✅ Android (API level 21+)
- ✅ iOS (iOS 12.0+)
- ✅ Foldable devices support
- ✅ Tablet optimization
## 🌐 Localization
The app supports multiple languages:
- English (en-US)
- Arabic (ar-SA)
## 🔧 Configuration
### Environment Setup
1. **API Keys**: Configure your API keys in the appropriate configuration files
2. **Firebase**: Set up Firebase project and add configuration files
3. **Maps**: Add Google Maps API key
4. **Payment**: Configure PayFort credentials
### Build Variants
- **Debug**: Development build with debugging enabled
- **Release**: Production-ready optimized build
## 📝 Development Guidelines
### Code Style
- Follow Dart/Flutter best practices
- Use consistent naming conventions
- Implement proper error handling
- Write comprehensive documentation
### Testing
```bash
# Run unit tests
flutter test
# Run integration tests
flutter test integration_test/
```
### Building for Production
**Android:**
```bash
flutter build apk --release
# or
flutter build appbundle --release
```
**iOS:**
```bash
flutter build ios --release
```
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/new-feature`)
3. Commit your changes (`git commit -am 'Add new feature'`)
4. Push to the branch (`git push origin feature/new-feature`)
5. Create a Pull Request
## 📄 License
This project is proprietary software developed for HMG Healthcare Group.
**Built with ❤️ for better healthcare accessibility**

@ -11,23 +11,108 @@ extension ResponsiveExtension on num {
double get _screenHeight => SizeUtils.height;
/// Scale text size
/// Check if device is likely a foldable
bool get _isFoldable {
double aspectRatio = _screenWidth / _screenHeight;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (_screenWidth > 700 || _screenHeight > 700);
}
/// Scale text size - enhanced for foldable devices
double get f {
double aspectRatio = _screenWidth / _screenHeight;
double scale = (_screenWidth < _screenHeight ? _screenWidth : _screenHeight) / figmaDesignWidth;
double clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2;
// Enhanced clamping for different device types
double clamp;
if (SizeUtils.deviceType == DeviceType.tablet || _isFoldable) {
// More conservative scaling for tablets and foldables
clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.4 : 1.1;
} else {
// Original logic for phones
clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2;
}
if (scale > clamp) scale = clamp;
return this * scale;
}
/// Scale horizontally (width-based)
double get w => (this * _screenWidth) / figmaDesignWidth;
/// Scale horizontally (width-based) - enhanced for foldable devices
double get w {
double baseScale = (this * _screenWidth) / figmaDesignWidth;
/// Scale vertically (height-based)
double get h => (this * _screenHeight) / figmaDesignHeight;
if (_isFoldable) {
// For foldables, use more conservative width scaling
double scale = _screenWidth / figmaDesignWidth;
scale = scale.clamp(0.8, 1.4);
return this * scale;
}
//radius
double get r => (this * _screenWidth) / figmaDesignWidth;
return baseScale;
}
/// Scale vertically (height-based) - enhanced for foldable devices
double get h {
double baseScale = (this * _screenHeight) / figmaDesignHeight;
if (_isFoldable) {
// For foldables, use height-based scaling but with constraints
double scale = (_screenHeight / figmaDesignHeight).clamp(0.8, 1.4);
return this * scale;
}
return baseScale;
}
/// Radius - enhanced for foldable devices
double get r {
double baseScale = (this * _screenWidth) / figmaDesignWidth;
if (_isFoldable) {
// Use the same logic as enhanced width for foldables
double scale = _screenWidth / figmaDesignWidth;
scale = scale.clamp(0.8, 1.4);
return this * scale;
}
return baseScale;
}
// New enhanced getters (additional options)
/// Enhanced font scaling with device-specific adjustments
double get fh {
double baseScale = _screenHeight / figmaDesignHeight;
if (_isFoldable) {
// Special handling for foldable devices - use more conservative scaling
baseScale = baseScale.clamp(0.8, 1.3);
} else if (SizeUtils.deviceType == DeviceType.tablet) {
// Tablet-specific scaling
baseScale = baseScale.clamp(0.9, 1.5);
} else {
// Phone scaling
baseScale = baseScale.clamp(0.8, 1.8);
}
return this * baseScale;
}
/// Adaptive scaling - automatically chooses best scaling method
double get adaptive {
if (_isFoldable) {
return fh;
} else if (SizeUtils.deviceType == DeviceType.tablet) {
return f * 0.9; // Slightly smaller for tablets
}
return f;
}
/// Minimum size constraint (useful for touch targets)
double get minSize {
double scaled = adaptive;
return scaled < 44 ? 44 : scaled; // Minimum 44pt for accessibility
}
/// Optional: direct accessors for full width/height
static double get screenWidth => SizeUtils.width;
@ -140,3 +225,9 @@ bool get isTablet => SizeUtils.deviceType == DeviceType.tablet;
bool get isMobile => SizeUtils.deviceType == DeviceType.mobile;
bool get isDesktop => SizeUtils.deviceType == DeviceType.desktop;
bool get isFoldable {
double aspectRatio = SizeUtils.width / SizeUtils.height;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (SizeUtils.width > 700 || SizeUtils.height > 700);
}

@ -633,17 +633,19 @@ class Utils {
required String url,
required Color iconColor,
bool isDisabled = false,
double width = 24,
double height = 24,
double? width,
double? height,
}) {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
return SvgPicture.network(
url,
colorFilter: ColorFilter.mode(
isDisabled ? iconColor.withOpacity(0.5) : iconColor,
BlendMode.srcIn,
),
width: width,
height: height,
width: iconW,
height: iconH,
);
}
@ -662,7 +664,7 @@ class Utils {
return Container(
decoration: BoxDecoration(
border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null,
borderRadius: border != null ? BorderRadius.circular(borderRadius ?? 0) : null,
borderRadius: border != null ? BorderRadius.circular(borderRadius ?? 12.r) : null,
),
child: Image.asset(icon, width: iconW, height: iconH, fit: fit),
);
@ -670,17 +672,27 @@ class Utils {
}
/// Widget to build an SVG from network
static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover, ImageErrorWidgetBuilder? errorBuilder}) {
static Widget buildImgWithNetwork({
required String url,
required Color iconColor,
bool isDisabled = false,
double? width,
double? height,
BoxFit fit = BoxFit.cover,
ImageErrorWidgetBuilder? errorBuilder,
}) {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
return Image.network(
url,
width: width,
height: height,
width: iconW,
height: iconH,
fit: fit,
errorBuilder: errorBuilder??(_,__,___){
//todo change the error builder icon that it is returning
return Utils.buildSvgWithAssets(width: width,
height: height,icon: AppAssets.no_visit_icon);
},
errorBuilder: errorBuilder ??
(_, __, ___) {
//todo change the error builder icon that it is returning
return Utils.buildSvgWithAssets(width: iconW, height: iconH, icon: AppAssets.no_visit_icon);
},
);
}

@ -167,16 +167,17 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null),
);
Widget toText14(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines,
double? letterSpacing = 0,
double? height,
TextOverflow? textOverflow}) =>
Widget toText14({
Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines,
double? letterSpacing = 0,
double? height,
TextOverflow? textOverflow,
}) =>
Text(
this,
textAlign: isCenter ? TextAlign.center : null,

@ -16,18 +16,16 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/AppointmentFilter.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
import '../../widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
import '../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight;
class MyAppointmentsPage extends StatefulWidget {
const MyAppointmentsPage({super.key});
@ -86,18 +84,14 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
}
Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) {
return getAppointList(
myAppointmentsVM, myAppointmentsVM.filteredAppointmentList);
return getAppointList(myAppointmentsVM, myAppointmentsVM.filteredAppointmentList);
}
Widget getAppointList(MyAppointmentsViewModel myAppointmentsVM,
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
Widget getAppointList(MyAppointmentsViewModel myAppointmentsVM, List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: myAppointmentsVM.availableFilters.isNotEmpty,
child: getAppointmentFilters(myAppointmentsVM)),
Visibility(visible: myAppointmentsVM.availableFilters.isNotEmpty, child: getAppointmentFilters(myAppointmentsVM)),
ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
@ -110,14 +104,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
itemBuilder: (context, index) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
PatientAppointmentHistoryResponseModel(),
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: true,
@ -134,16 +123,10 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
filteredAppointmentList[index],
myAppointmentsViewModel:
myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: filteredAppointmentList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: false,
@ -154,8 +137,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
)
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any appointments yet."
.needTranslation,
noDataText: "You don't have any appointments yet.".needTranslation,
callToActionButton: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
@ -168,18 +150,17 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingSymmetrical(48.h, 0.h),
);
},
separatorBuilder: (BuildContext cxt, int index) =>
SizedBox(height: 16.h),
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 24.h),
],
@ -189,45 +170,41 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
Widget getAppointmentFilters(MyAppointmentsViewModel myAppointmentsVM) {
return SizedBox(
child: Row(
children: [
Expanded(
child: ListView.separated(
separatorBuilder: (_, index) => SizedBox(
width: 8.h,
),
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.availableFilters.length,
itemBuilder: (_, index) => AppointmentFilters(
selectedFilter: myAppointmentsVM.selectedFilter,
item: myAppointmentsVM.availableFilters[index],
onClicked: () {
if (myAppointmentsVM.availableFilters[index] ==
AppointmentListingFilters.DATESELECTION) {
showCommonBottomSheetWithoutHeight(
title: "Set The Date Range".needTranslation,
context,
child: DateRangeSelector(
onRangeSelected: (start, end) {
// if (start != null) {
myAppointmentsVM.getSelectedDateRange(
start, end);
// }
},
),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
} else {
myAppointmentsVM.setSelectedFilter(
myAppointmentsVM.availableFilters[index]);
myAppointmentsVM.filterTheListAsPerSelection();
}
},
)),
),
],
)).paddingOnly(top: 24.h, left: 24.h, right: 24.h);
children: [
Expanded(
child: ListView.separated(
separatorBuilder: (_, index) => SizedBox(
width: 8.h,
),
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.availableFilters.length,
itemBuilder: (_, index) => AppointmentFilters(
selectedFilter: myAppointmentsVM.selectedFilter,
item: myAppointmentsVM.availableFilters[index],
onClicked: () {
if (myAppointmentsVM.availableFilters[index] == AppointmentListingFilters.DATESELECTION) {
showCommonBottomSheetWithoutHeight(
title: "Set The Date Range".needTranslation,
context,
child: DateRangeSelector(
onRangeSelected: (start, end) {
// if (start != null) {
myAppointmentsVM.getSelectedDateRange(start, end);
// }
},
),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
} else {
myAppointmentsVM.setSelectedFilter(myAppointmentsVM.availableFilters[index]);
myAppointmentsVM.filterTheListAsPerSelection();
}
},
)),
),
],
)).paddingOnly(top: 24.h, left: 24.h, right: 24.h);
}
}

@ -106,392 +106,388 @@ class _LandingPageState extends State<LandingPage> {
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: SingleChildScrollView(
padding: EdgeInsets.only(top: kToolbarHeight + 0.h, bottom: 24),
child: Column(
spacing: 16.h,
children: [
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
appState.isAuthenticated
? WelcomeWidget(
onTap: () {
Navigator.of(context).push(springPageRoute(ProfileSettings()));
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg,
).expanded
: CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authVM.onLoginPressed();
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
height: 40.h,
),
Row(
mainAxisSize: MainAxisSize.min,
spacing: 12.h,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
),
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
),
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
return PopScope(
canPop: false,
child: Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: SingleChildScrollView(
padding: EdgeInsets.only(top: kToolbarHeight + 0.h, bottom: 24),
child: Column(
spacing: 16.h,
children: [
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
appState.isAuthenticated
? WelcomeWidget(
onTap: () {
Navigator.of(context).push(springPageRoute(ProfileSettings()));
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg,
).expanded
: CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authVM.onLoginPressed();
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
height: 40.h,
),
);
}),
],
)
],
).paddingSymmetrical(24.h, 0.h),
appState.isAuthenticated
? Column(
Row(
mainAxisSize: MainAxisSize.min,
spacing: 12.h,
children: [
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Appointments & Visits".toText16(isBold: true),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyAppointmentsPage(),
page: MedicalFilePage(),
// page: LoginScreen(),
),
);
}),
SizedBox(height: 16.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: true,
isFromHomePage: true,
),
).paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientAppointmentsHistoryList.length == 1
? Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first,
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
).paddingSymmetrical(24.h, 0.h)
: Swiper(
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 3
: myAppointmentsVM.patientAppointmentsHistoryList.length < 3
? myAppointmentsVM.patientAppointmentsHistoryList.length
: 3,
layout: SwiperLayout.STACK,
loop: true,
itemWidth: MediaQuery.of(context).size.width - 48.h,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 210 + 25,
pagination: const SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 210 + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
),
);
}),
],
),
],
).paddingSymmetrical(24.h, 0.h),
appState.isAuthenticated
? Column(
children: [
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Appointments & Visits".toText16(isBold: true),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
SizedBox(height: 16.h),
Consumer<MyAppointmentsViewModel>(
builder: (context, myAppointmentsVM, child) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: true,
isFromHomePage: true,
),
).paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientAppointmentsHistoryList.length == 1
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first,
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
);
},
)
: Container(
width: double.infinity,
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, 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),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: BookAppointmentPage(),
).paddingSymmetrical(24.h, 0.h)
: Swiper(
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 3
: myAppointmentsVM.patientAppointmentsHistoryList.length < 3
? myAppointmentsVM.patientAppointmentsHistoryList.length
: 3,
layout: SwiperLayout.STACK,
loop: true,
itemWidth: MediaQuery.of(context).size.width - 48.h,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 210 + 25,
pagination: const SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 210 + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
),
],
),
),
).paddingSymmetrical(24.h, 0.h);
}),
Consumer<ImmediateLiveCareViewModel>(builder: (context, immediateLiveCareVM, child) {
return immediateLiveCareVM.patientHasPendingLiveCareRequest
? Column(
children: [
SizedBox(height: 12.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
)
: Container(
width: double.infinity,
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Column(
children: [
AppCustomChipWidget(
labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
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),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
height: (isFoldable || isTablet) ? 56.h : 46.h,
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
).paddingSymmetrical(24.h, 0.h);
},
),
Consumer<ImmediateLiveCareViewModel>(
builder: (context, immediateLiveCareVM, child) {
return immediateLiveCareVM.patientHasPendingLiveCareRequest
? Column(
children: [
SizedBox(height: 12.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: true,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"You have a pending LiveCare request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"You have a pending LiveCare request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
),
],
),
],
),
],
),
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()));
}),
SizedBox(height: 12.h),
],
)
: SizedBox(height: 12.h);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Quick Links".needTranslation.toText16(isBold: true),
Row(
children: [
"View medical file".needTranslation.toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage()));
}),
SizedBox(height: 16.h),
Container(
height: 120.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getLoggedInServiceCardsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SmallServiceCard(
icon: LandingPageData.getLoggedInServiceCardsList[index].icon,
title: LandingPageData.getLoggedInServiceCardsList[index].title,
subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle,
iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor,
textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor,
backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor,
isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold,
serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName,
),
),
),
);
}),
SizedBox(height: 12.h),
],
)
: SizedBox(height: 12.h);
}),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Quick Links".needTranslation.toText16(isBold: true),
Row(
children: [
"View medical file".needTranslation.toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
},
separatorBuilder: (BuildContext cxt, int index) => 10.width,
),
),
],
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
),
);
}),
SizedBox(height: 16.h),
Container(
height: 120.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
child: Column(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getLoggedInServiceCardsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SmallServiceCard(
icon: LandingPageData.getLoggedInServiceCardsList[index].icon,
title: LandingPageData.getLoggedInServiceCardsList[index].title,
subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle,
iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor,
textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor,
backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor,
isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold,
serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName,
),
).paddingSymmetrical(24.h, 0.h),
],
)
: Container(
height: 127.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getNotLoggedInServiceCardsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SmallServiceCard(
serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName,
icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon,
title: LandingPageData.getNotLoggedInServiceCardsList[index].title,
subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle,
iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor,
textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor,
backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor,
isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold,
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => 10.width,
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => 0.width,
),
],
),
).paddingSymmetrical(24.h, 0.h),
),
],
),
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Services".toText16(isBold: true),
Row(
children: [
"View all services".toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
)
: Container(
height: 127.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
),
child: Column(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getNotLoggedInServiceCardsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SmallServiceCard(
serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName,
icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon,
title: LandingPageData.getNotLoggedInServiceCardsList[index].title,
subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle,
iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor,
textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor,
backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor,
isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold,
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => 0.width,
),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 340.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.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: LargeServiceCard(
image: LandingPageData.getServiceCardsList[index].icon,
title: LandingPageData.getServiceCardsList[index].title,
subtitle: LandingPageData.getServiceCardsList[index].subtitle,
icon: LandingPageData.getServiceCardsList[index].largeCardIcon,
),
),
],
),
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Services".toText16(isBold: true),
Row(
children: [
"View all services".toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 350.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.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: LargeServiceCard(
image: LandingPageData.getServiceCardsList[index].icon,
title: LandingPageData.getServiceCardsList[index].title,
subtitle: LandingPageData.getServiceCardsList[index].subtitle,
icon: LandingPageData.getServiceCardsList[index].largeCardIcon,
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.w),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.w),
),
),
),
appState.isAuthenticated ? HabibWalletCard() : SizedBox(),
],
appState.isAuthenticated ? HabibWalletCard() : SizedBox(),
],
),
),
),
);
@ -503,19 +499,21 @@ class _LandingPageState extends State<LandingPage> {
title: "",
isCloseButtonVisible: false,
child: StatefulBuilder(builder: (context, setState) {
return QuickLogin(
isDone: isDone,
onPressed: () {
// sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true);
authVM.loginWithFingerPrintFace(() {
isDone = true;
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true);
setState(() {});
});
},
);
}),
child: StatefulBuilder(
builder: (context, setState) {
return QuickLogin(
isDone: isDone,
onPressed: () {
// sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true);
authVM.loginWithFingerPrintFace(() {
isDone = true;
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true);
setState(() {});
});
},
);
},
),
// height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3,
isFullScreen: false,
callBackFunc: () {

@ -26,40 +26,37 @@ class LargeServiceCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
return Container(
width: 150.w,
padding: EdgeInsets.symmetric(horizontal: 3.w),
child: Container(
width: 150.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.transparent, borderRadius: 16.r),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(AppAssets.livecare_service, width: 220.w, height: 220.h, fit: BoxFit.contain),
SizedBox(height: 3.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: icon, width: 24.w, height: 24.h),
title.toText14(color: AppColors.blackColor, isBold: true),
],
),
subtitle.toText11(color: AppColors.blackColor),
SizedBox(height: 10.h),
CustomButton(
width: 150.w,
text: LocaleKeys.bookNow.tr(context: context),
onPressed: () {},
backgroundColor: AppColors.borderOnlyColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.bold,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
),
],
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.transparent, borderRadius: 16.r),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(AppAssets.livecare_service, width: 220.w, fit: BoxFit.contain),
SizedBox(height: 10.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: icon, width: 24.w, height: 24.h),
Flexible(child: title.toText14(color: AppColors.blackColor, isBold: true, textOverflow: TextOverflow.clip, maxlines: 1)),
],
),
subtitle.toText11(color: AppColors.blackColor),
SizedBox(height: 10.h),
CustomButton(
text: LocaleKeys.bookNow.tr(context: context),
onPressed: () {},
backgroundColor: AppColors.borderOnlyColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.bold,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
),
],
),
);
}

@ -221,12 +221,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
AppCustomChipWidget(
labelText: "${appState.getAuthenticatedUser()!.age} Years Old",
labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w),
),
AppCustomChipWidget(
icon: AppAssets.blood_icon,
labelText: "Blood: ${appState.getUserBloodGroup}",
labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}",
iconColor: AppColors.primaryRedColor,
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 8.w),
labelPadding: EdgeInsetsDirectional.only(end: 8.w),
),
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return AppCustomChipWidget(
@ -376,7 +377,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
height: isFoldable ? 50.h : 40.h,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
),
@ -815,7 +816,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 56.h,
height: isFoldable ? 50.h : 40.h,
).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h),
),
).paddingSymmetrical(24.w, 0.h);
@ -857,19 +858,21 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
MedicalFileCard(
label: "My Invoices List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 36.w),
label: "My Invoices List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 36.w,
),
MedicalFileCard(
label: "Ancillary Orders List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 36.w),
label: "Ancillary Orders List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 36.w,
),
],
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),

@ -53,7 +53,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
left: false,
right: false,
child: Column(
spacing: 24,
spacing: 24.h,
children: [
PageView(
controller: pageController,
@ -75,24 +75,24 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
},
).expanded,
Row(
spacing: 4.h,
spacing: 4.w,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 250),
height: 6.h,
width: selectedIndex == 0 ? 18.h : 6.h,
width: selectedIndex == 0 ? 18.w : 6.w,
decoration:
BoxDecoration(color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30)),
BoxDecoration(color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)),
),
AnimatedContainer(
duration: const Duration(milliseconds: 250),
height: 6.h,
width: selectedIndex == 1 ? 18.h : 6.h,
width: selectedIndex == 1 ? 18.w : 6.w,
decoration:
BoxDecoration(color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30)),
BoxDecoration(color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)),
),
],
).paddingOnly(left: 24.h, right: 24.h),
).paddingOnly(left: 24.w, right: 24.w),
Row(
children: [
AnimatedSwitcher(
@ -102,20 +102,20 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
? CustomButton(
text: "Skip".needTranslation,
onPressed: () => goToHomePage(),
width: 86.h,
width: 86.w,
height: 56.h,
backgroundColor: Color(0xffFEE9EA),
textColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
).paddingOnly(left: 24.h)
).paddingOnly(left: 24.w)
: const SizedBox.shrink(),
),
const Spacer(),
AnimatedContainer(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
width: selectedIndex == 0 ? 86.h : MediaQuery.of(context).size.width - 48.h,
margin: EdgeInsets.only(left: 24.h, right: 24.h),
width: selectedIndex == 0 ? 86.w : MediaQuery.of(context).size.width - 48.w,
margin: EdgeInsets.only(left: 24.w, right: 24.w),
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
borderRadius: BorderRadius.circular(12.r),
@ -126,8 +126,8 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
child: selectedIndex == 0
? CustomButton(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward,
iconSize: 32.h,
width: 86.h,
iconSize: 32.w,
width: 86.w,
height: 56.h,
text: "".needTranslation,
backgroundColor: Colors.transparent,
@ -146,7 +146,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
),
),
],
),
).paddingOnly(bottom: 10.h),
],
),
),
@ -157,7 +157,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
spacing: 12.h,
children: [
Align(
alignment: Alignment.bottomCenter,
@ -167,10 +167,9 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
repeat: true,
reverse: false,
frameRate: FrameRate(60),
width: MediaQuery.sizeOf(context).width - 50,
height: MediaQuery.sizeOf(context).width - 50)))
width: MediaQuery.sizeOf(context).width - 50.w,
height: MediaQuery.sizeOf(context).width - 50.w)))
.expanded,
// 12.height,
Text(
heading,
style: TextStyle(fontSize: 36.f, fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -0.4, height: 1),
@ -180,6 +179,6 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
style: TextStyle(fontSize: 16.f, fontWeight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: 0, height: 26 / 16),
),
],
).paddingOnly(left: 24.h, right: 24.h);
).paddingOnly(left: 24.w, right: 24.w);
}
}

@ -51,6 +51,33 @@ class _ProfileSettingsState extends State<ProfileSettings> {
super.dispose();
}
double dynamicItemHeight(BuildContext context) {
final double w = SizeUtils.width;
final double h = SizeUtils.height;
double longer = w > h ? w : h;
double shorter = w < h ? w : h;
final double aspect = longer / (shorter == 0 ? 1 : shorter);
// Choose multiplier based on aspect ratio (handles near-square / foldable)
double multiplier;
if (aspect < 1.05) {
multiplier = 0.28; // nearly square / foldable -> smaller card height
} else if (aspect > 1.8) {
multiplier = 0.40; // very tall/wide -> larger height
} else {
multiplier = 0.34; // normal phones/tablets
}
// Compute and clamp using sensible bounds (uses .h extension)
final double minH = 210.h;
final double maxH = 380.h;
final double computed = (shorter * multiplier);
return computed.clamp(minH, maxH);
}
int length = 3;
final SwiperController _controller = SwiperController();
@ -72,11 +99,11 @@ class _ProfileSettingsState extends State<ProfileSettings> {
itemCount: medicalVm.patientFamilyFiles.length,
layout: SwiperLayout.STACK,
loop: true,
itemWidth: SizeUtils.width - 42.w,
itemHeight: dynamicItemHeight(context),
itemWidth: SizeUtils.width - 30.w,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 220.h,
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: (210.h + 8.h + 24.h)),
@ -97,13 +124,14 @@ class _ProfileSettingsState extends State<ProfileSettings> {
onFamilySwitchPress: (FamilyFileResponseModelLists profile) {
medicalVm.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber);
},
).paddingOnly(right: 16.w);
).paddingOnly(right: 16.w, left: 8.w);
},
),
SizedBox(height: 5.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 9.w, mainAxisSpacing: 9.h),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 3 : 2),
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(left: 24.w, right: 24.w, bottom: 24.w, top: 10.w),
padding: EdgeInsets.only(left: 24.w, right: 24.w, bottom: 24.h),
shrinkWrap: true,
children: [
Container(
@ -115,14 +143,14 @@ class _ProfileSettingsState extends State<ProfileSettings> {
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.h,
// spacing: 4.h,
children: [
Row(
spacing: 8.w,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h),
"Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded,
"Habib Wallet".needTranslation.toText16(weight: FontWeight.w600, maxlines: 2).expanded,
Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward),
],
),
@ -131,10 +159,11 @@ class _ProfileSettingsState extends State<ProfileSettings> {
return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false)
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h);
}),
Spacer(),
CustomButton(
height: 40.h,
icon: AppAssets.recharge_icon,
iconSize: 24.h,
iconSize: 22.w,
iconColor: AppColors.infoColor,
textColor: AppColors.infoColor,
text: "Recharge".needTranslation,
@ -152,7 +181,7 @@ class _ProfileSettingsState extends State<ProfileSettings> {
).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage()));
}),
)
),
],
),
"Quick Actions"
@ -220,7 +249,12 @@ class _ProfileSettingsState extends State<ProfileSettings> {
],
),
),
CustomButton(icon: AppAssets.minus, text: "Deactivate account".needTranslation, onPressed: () {}).paddingAll(24.w),
CustomButton(
height: 56.h,
icon: AppAssets.minus,
text: "Deactivate account".needTranslation,
onPressed: () {},
).paddingAll(24.w),
],
);
},
@ -275,7 +309,11 @@ class FamilyCardWidget extends StatelessWidget {
final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false;
final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID);
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
children: [
Column(
@ -284,7 +322,6 @@ class FamilyCardWidget extends StatelessWidget {
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
//
children: [
Image.asset(profile.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.w, height: 56.h),
Column(
@ -404,7 +441,7 @@ class FamilyCardWidget extends StatelessWidget {
borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor,
textColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor,
iconColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor,
height: 56.h,
height: isFoldable ? 50.h : 40.h,
fontSize: 14.f,
).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h);
}
@ -420,7 +457,7 @@ class FamilyCardWidget extends StatelessWidget {
textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor,
iconColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor,
onPressed: canSwitchBack ? () => onFamilySwitchPress(profile) : () {},
height: 40.h,
height: isFoldable ? 50.h : 40.h,
fontSize: 14.f,
).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h);
}

@ -56,7 +56,7 @@ class CustomButton extends StatelessWidget {
return GestureDetector(
onTap: isDisabled ? null : onPressed,
child: Container(
height: height ?? (50.h),
height: height ?? (56.h),
width: width,
padding: padding,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(

@ -88,7 +88,7 @@ class CustomTabBarState extends State<CustomTabBar> {
bool isSelected = selectedIndex == currentIndex;
return Container(
height: 54.h,
padding: EdgeInsets.only(top: 4.h, bottom: 4.h, left: 16.w, right: 16.w),
padding: EdgeInsets.only(top: 4.h, bottom: 4.h, left: 14.w, right: 14.w),
alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: isSelected ? widget.activeBackgroundColor : widget.inActiveBackgroundColor,

Loading…
Cancel
Save