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

Reviewed-on: #253
dev_aamir
khansultan1 11 hours ago
commit 1e65a67f0e

@ -1570,7 +1570,7 @@
"prediction": "التوقعات",
"prevention": "الوقاية",
"disclaimer": "إخلاء مسؤولية",
"thisAboveInfo": "المعلومات أعلاه مُوَلّدة بواسطة الذكاء الاصطناعي بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.",
"thisAboveInfo": "تم تحليل نتائج المختبرهذه بواسطة الذكاء الاصطناعي، وهي لا تُعدّ نصيحة طبية. استشر طبيبك المختص للتشخيص والعلاج.",
"aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟",
"consent": "نتائج المختبر",
"bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.",
@ -1755,5 +1755,9 @@
"errorFetchingData": "حدث خطأ أثناء تحميل البيانات",
"negative": "سلبي",
"calculateCaloriesBasedOnFactors": "حساب السعرات الحرارية اليومية بناءً على عدة عوامل مثل الطول، الوزن، العمر، الجنس، والنشاط البدني اليومي.",
"conformationCall": "ستتلقى مكالمة لتأكيد طلبك قريباً."
"conformationCall": "ستتلقى مكالمة لتأكيد طلبك قريباً.",
"radiologyLabResults": "نتائج الأشعة",
"disclaimerRadiology": "تم تحليل نتائج الأشعة هذه بواسطة الذكاء الاصطناعي، وهي لا تُعدّ نصيحة طبية. استشر طبيبك المختص للتشخيص والعلاج."
}

@ -1572,7 +1572,7 @@
"prediction": "Prediction",
"prevention": "Prevention",
"disclaimer": "Disclaimer",
"thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.",
"thisAboveInfo": "This Lab result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.",
"aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"consent": "Consent",
"bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.",
@ -1743,7 +1743,11 @@
"finda": "Find a",
"calculateCaloriesBasedOnFactors": "Calculates daily calorie intake based on several factors including: height, weight, age, gender and daily physical activity.",
"insurance2": "Insurance",
"conformationCall": "You will receive a call from HMG for confirmation "
"conformationCall": "You will receive a call from HMG for confirmation ",
"radiologyLabResults": "Radiology Results",
"disclaimerRadiology": "This radiology result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment."
}

@ -51,12 +51,15 @@ _incomingCall(Map<String, dynamic> data) async {
// GetIt.instance<CacheService>().saveString(key: CacheConst.zoomRoomID, value: roomID);
// GetIt.instance<CacheService>().saveBool(key: CacheConst.isAppOpenedFromCall, value: true);
Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID);
Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID);
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
WidgetsFlutterBinding.ensureInitialized();
int callKitType = callTypeID == '2' ? 0 : 1;
var _currentUuid = Uuid().v4();
CallKitParams callKitParams = CallKitParams(
id: _currentUuid,
@ -64,7 +67,7 @@ _incomingCall(Map<String, dynamic> data) async {
appName: 'Dr Al Habib',
avatar: 'https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG',
// handle: LandingPage.incomingCallData.name,
type: 1,
type: callKitType,
textAccept: 'Accept',
textDecline: 'Decline',
missedCallNotification: NotificationParams(
@ -80,7 +83,11 @@ _incomingCall(Map<String, dynamic> data) async {
callbackText: 'Hang Up',
),
duration: 30000,
extra: <String, dynamic>{'userId': '1a2b3c4d'},
extra: <String, dynamic>{
'userId': '1a2b3c4d',
'callTypeID': callTypeID, // Store actual callTypeID here
'roomID': roomID, // Store roomID here for backup
},
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: const AndroidParams(
isImportant: true,

@ -42,7 +42,7 @@ class RadiologyRepoImp implements RadiologyRepo {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
final radOrders;
List<PatientRadiologyResponseModel> radOrders = [];
try {
if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) {
final list = response['FinalRadiologyList'];
@ -52,10 +52,18 @@ class RadiologyRepoImp implements RadiologyRepo {
.cast<PatientRadiologyResponseModel>();
} else {
final list = response['FinalRadiologyListAPI'];
radOrders = list
.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<PatientRadiologyResponseModel>();
if(list != null && list.length != 0) {
radOrders = list
.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<PatientRadiologyResponseModel>();
}
}
// Check if radOrders is empty and set failure
if (radOrders.isEmpty) {
failure = ServerFailure(errorMessage ?? "No radiology orders found");
return;
}
apiResponse = GenericApiModel<List<PatientRadiologyResponseModel>>(
@ -203,6 +211,12 @@ class RadiologyRepoImp implements RadiologyRepo {
}
}
// Check if radOrders is empty and set failure
if (radOrders.isEmpty) {
failure = ServerFailure(errorMessage ?? "No radiology orders found for this appointment");
return;
}
apiResponse = GenericApiModel<List<PatientRadiologyResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,

@ -1746,5 +1746,7 @@ abstract class LocaleKeys {
static const negative = 'negative';
static const calculateCaloriesBasedOnFactors = 'calculateCaloriesBasedOnFactors';
static const conformationCall = 'conformationCall';
static const radiologyLabResults = 'radiologyLabResults';
static const disclaimerRadiology = 'disclaimerRadiology';
}

@ -430,8 +430,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
const Spacer(),
BellAnimatedSwitch(
key: _bellSwitchKey,
width: 48.w,
height: 30.h,
width: 56.w,
height: 38.h,
initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false,
activeColor: AppColors.successColor.withOpacity(0.2),
inactiveColor: AppColors.lightGrayBGColor,

@ -717,35 +717,37 @@ class _AppointmentCardState extends State<AppointmentCard> {
// Countdown Timer - DD : HH : MM : SS format with labels
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // Days
// _buildTimeUnit(
// _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
// LocaleKeys.days.tr(context: context),
// ),
// _buildTimeSeparator(),
// // Hours
// _buildTimeUnit(
// _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
// LocaleKeys.hours.tr(context: context),
// ),
// _buildTimeSeparator(),
// // Minutes
// _buildTimeUnit(
// _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
// LocaleKeys.minutes.tr(context: context),
// ),
// _buildTimeSeparator(),
// // Seconds
// _buildTimeUnit(
// _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
// LocaleKeys.seconds.tr(context: context),
// ),
// ],
// ),
Directionality(
textDirection: ui.TextDirection.ltr,
child:Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Days
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
LocaleKeys.days.tr(context: context),
),
_buildTimeSeparator(),
// Hours
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
LocaleKeys.hours.tr(context: context),
),
_buildTimeSeparator(),
// Minutes
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.minutes.tr(context: context),
),
_buildTimeSeparator(),
// Seconds
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.seconds.tr(context: context),
),
],
)),
SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon
CustomButton(

@ -151,7 +151,7 @@ class AppointmentDoctorCard extends StatelessWidget {
),
),
patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! || patientAppointmentHistoryResponseModel.isActiveDoctor! == false
patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();

@ -70,7 +70,8 @@ class LoginScreenState extends State<LoginScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover),
SizedBox(height: 130.h), // Adjusted to sizer unit
// SizedBox(height: 130.h),
SizedBox(height: MediaQuery.of(context).size.height * 0.12), // based on the screen height Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor ),
SizedBox(height: 32.h),
Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget

@ -135,8 +135,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.bloodSugar.tr().toText16(isBold: true),
LocaleKeys.trackYourGlucoseLevels.tr().toText12(
LocaleKeys.bloodSugar.tr(context: context).toText16(isBold: true),
LocaleKeys.trackYourGlucoseLevels.tr(context: context).toText12(
isBold: true,
color: Color(0xFF8F9AA3),
)
@ -168,7 +168,7 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [LocaleKeys.bloodCholesterol.tr().toText16(isBold: true), LocaleKeys.monitorCholesterolLevels.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))],
children: [LocaleKeys.bloodCholesterol.tr(context: context).toText16(isBold: true), LocaleKeys.monitorCholesterolLevels.tr(context: context).toText12(isBold: true, color: Color(0xFF8F9AA3))],
),
),
SizedBox(width: 12.w),
@ -195,8 +195,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.triglyceridesFatBlood.tr().toText16(isBold: true),
LocaleKeys.understandTriglyceridesImpact.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))
LocaleKeys.triglyceridesFatBlood.tr(context: context).toText16(isBold: true),
LocaleKeys.understandTriglyceridesImpact.tr(context: context).toText12(isBold: true, color: Color(0xFF8F9AA3))
],
),
),

@ -154,7 +154,8 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
}
// Show success message and pop back
void _showSuccessAndPop() {
void _showSuccessAndPop() async{
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(
@ -164,7 +165,13 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
},
isCloseButtonVisible: false,
isDismissible: true,
isFullScreen: false, isAutoDismiss: true);
isFullScreen: false, isAutoDismiss: false);
await Future.delayed(Duration(milliseconds: 2000)).then((value) {
if (mounted){
Navigator.pop(context);
Navigator.pop(context);
}
});
}
// Reusable method to build selection row widget
@ -366,7 +373,7 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
"Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor),
LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor),
unit.toText12(isBold: true, color: AppColors.textColor),
],
),
@ -473,7 +480,7 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
controller: dateController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Date",
labelText: LocaleKeys.date.tr(context: context),
hintText: LocaleKeys.pickADate.tr(context: context),
focusNode: FocusNode(),
isEnable: true,
@ -503,7 +510,7 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
controller: timeController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Time",
labelText: LocaleKeys.time.tr(context: context),
hintText: LocaleKeys.selectMeasureTime.tr(context: context),
focusNode: FocusNode(),
isEnable: true,

@ -84,7 +84,7 @@ class _HealthTrackersPageState extends State<HealthTrackersPage> {
iconBgColor: AppColors.primaryRedColor,
icon: AppAssets.bloodSugarOnlyIcon,
title: LocaleKeys.bloodSugar.tr(context: context),
description: "Track your glucose levels, understand trends, and get personalized insights for better health.",
description: LocaleKeys.trackYourGlucoseLevels.tr(context: context),
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,

@ -32,6 +32,7 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/service_info_page.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart';
@ -378,8 +379,8 @@ class _ServicesPageState extends State<ServicesPage> {
void initState() {
// TODO: implement initState
super.initState();
//weatherVM = getIt<WeatherMonitorViewModel>();
// weatherVM.fetchCityInfoList();
weatherVM = getIt<WeatherMonitorViewModel>();
weatherVM.fetchCityInfoList();
}
@ -390,85 +391,18 @@ class _ServicesPageState extends State<ServicesPage> {
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context);
hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.exploreServices.tr(),
isLeading: widget.showBackIcon,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
// Flexible(
// child: Container(
// padding: EdgeInsets.all(16.w),
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
// child: Column(
// children: [
// Row(
// children: [
// Utils.buildSvgWithAssets(icon: "assets/images/svg/cloudyweather.svg", width: 64.w, height: 64.w).onPress(() {
// // locationUtils?.getCurrentLocation();
// print(getIt.get<AppState>().userLat);
// print(getIt.get<AppState>().userLong);
// }),
// SizedBox(width: 12.w),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// "August 19, 2025 - Tuesday, 11:25 ".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
// SizedBox(
// height: 4.w,
// ),
// "Cloudy 38oC".toText16(isBold: true, weight: FontWeight.w600),
// SizedBox(
// height: 4.w,
// ),
// "Health tips based on current weather".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)
// ],
// ),
// ],
// ),
// SizedBox(height: 16.w),
// Container(
// padding: EdgeInsets.all(16.w),
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.lightGrayBGColor,
// borderRadius: 10.r,
// hasShadow: false,
// ),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Row(
// children: [
// Utils.buildSvgWithAssets(icon: "assets/images/svg/morning.svg", width: 24.w, height: 24.h),
// SizedBox(width: 4.w),
// DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
// ],
// ),
// SizedBox(width: 25.w),
// DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
// SizedBox(width: 25.w),
// Row(
// children: [
// Utils.buildSvgWithAssets(icon: "assets/images/svg/evening.svg", width: 24.w, height: 24.h),
// SizedBox(width: 4.w),
// DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
// ],
// ),
// ],
// ).expanded,
// ).paddingSymmetrical(0.w, 8.w)
// ],
// ),
// ),
// )
// ],
// ).paddingSymmetrical(24.w, 0.w),
SizedBox(height: 16.h),
return ChangeNotifierProvider.value(
value: weatherVM,
child: Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.exploreServices.tr(),
isLeading: widget.showBackIcon,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const WeatherWidget(),
SizedBox(height: 16.h),
LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
GridView.builder(
@ -910,6 +844,7 @@ class _ServicesPageState extends State<ServicesPage> {
],
),
),
),
);
}
}

@ -0,0 +1,194 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/weather/weather_details_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class WeatherWidget extends StatelessWidget {
final bool showButton;
const WeatherWidget({super.key, this.showButton = true});
String _getWeatherIcon(String? categoryValue) {
if (categoryValue == null) return "assets/images/svg/cloudyweather.svg";
final lowerValue = categoryValue.toLowerCase();
if (lowerValue.contains('sunny') || lowerValue.contains('clear')) {
return "assets/images/svg/morning.svg";
} else if (lowerValue.contains('cloudy') || lowerValue.contains('cloud')) {
return "assets/images/svg/cloudyweather.svg";
} else if (lowerValue.contains('rain')) {
return "assets/images/svg/cloudyweather.svg";
}
return "assets/images/svg/cloudyweather.svg";
}
String _formatDateTime(DateTime dateTime) {
final formatter = DateFormat('MMMM dd, yyyy - EEEE, HH:mm');
return formatter.format(dateTime);
}
@override
Widget build(BuildContext context) {
return Consumer<WeatherMonitorViewModel>(
builder: (context, weatherVM, child) {
final currentCity = weatherVM.cityInfoList.isNotEmpty
? weatherVM.cityInfoList.first
: null;
final now = DateTime.now();
final temperature = currentCity?.temperature;
final healthTip = LocaleKeys.healthTipsBasedOnCurrentWeather.tr();
return Row(
children: [
Flexible(
child: Container(
padding: EdgeInsets.all(16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: weatherVM.isLoading
? _buildLoadingState()
: Column(
children: [
Row(
children: [
Utils.buildSvgWithAssets(
icon: _getWeatherIcon(currentCity?.categoryValue),
width: 64.w,
height: 64.w,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_formatDateTime(now).toText12(
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
SizedBox(height: 4.w),
"$temperature°C"
.toText16(
isBold: true,
weight: FontWeight.w600,
),
SizedBox(height: 4.w),
healthTip.toText12(
color: AppColors.greyTextColor,
fontWeight: FontWeight.w500,
maxLine: 2,
),
],
),
),
],
),
if (showButton) ...[
SizedBox(height: 16.w),
CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: ChangeNotifierProvider.value(
value: weatherVM,
child: const WeatherDetailsPage(),
),
),
);
},
padding: EdgeInsets.zero,
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,
height: 40.h,
),
],
],
),
),
),
],
).paddingSymmetrical(24.w, 0.w);
},
);
}
Widget _buildLoadingState() {
return Column(
children: [
Row(
children: [
Container(
width: 64.w,
height: 64.w,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(12.r),
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
height: 12.h,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(6.r),
),
),
SizedBox(height: 8.h),
Container(
width: 100.w,
height: 16.h,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(8.r),
),
),
SizedBox(height: 8.h),
Container(
width: double.infinity,
height: 12.h,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(6.r),
),
),
],
),
),
],
),
SizedBox(height: 16.w),
Container(
height: 56.h,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(10.r),
),
),
],
);
}
}

@ -53,8 +53,16 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
children: [
AppCustomChipWidget(
labelText: insuranceApprovalResponseModel.approvalStatusDescption,
backgroundColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor.withOpacity(0.1) : AppColors.primaryRedColor.withOpacity(0.1),
textColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor : AppColors.primaryRedColor,
backgroundColor: insuranceApprovalResponseModel.status == 9
? AppColors.successColor.withValues(alpha: 0.1)
: (insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase().contains('partial') == true || insuranceApprovalResponseModel.status == 8)
? AppColors.warningColorYellow.withValues(alpha: 0.1)
: AppColors.primaryRedColor.withValues(alpha: 0.1),
textColor: insuranceApprovalResponseModel.status == 9
? AppColors.successColor
: (insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase().contains('partial') == true || insuranceApprovalResponseModel.status == 8)
? AppColors.warningColorYellow
: AppColors.primaryRedColor,
),
AppCustomChipWidget(
icon: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon),
@ -65,7 +73,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
),
AppCustomChipWidget(
labelText: appState.isArabic() ? insuranceApprovalResponseModel.isInOutPatientDescriptionN! : insuranceApprovalResponseModel.isInOutPatientDescription!,
backgroundColor: AppColors.warningColorYellow.withOpacity(0.1),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow,
),
],

@ -24,6 +24,34 @@ class InsuranceApprovalCard extends StatelessWidget {
AppState appState;
bool isLoading;
/// Helper method to determine if approval is partially approved
bool _isPartiallyApproved() {
final description = insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase() ?? '';
return description.contains('partial') || insuranceApprovalResponseModel.status == 8;
}
/// Helper method to get chip background color based on approval status
Color _getChipBackgroundColor() {
if (insuranceApprovalResponseModel.status == 9) {
return AppColors.successColor.withValues(alpha: 0.1);
} else if (_isPartiallyApproved()) {
return AppColors.warningColorYellow.withValues(alpha: 0.1);
} else {
return AppColors.primaryRedColor.withValues(alpha: 0.1);
}
}
/// Helper method to get chip text color based on approval status
Color _getChipTextColor() {
if (insuranceApprovalResponseModel.status == 9) {
return AppColors.successColor;
} else if (_isPartiallyApproved()) {
return AppColors.warningColorYellow;
} else {
return AppColors.primaryRedColor;
}
}
@override
Widget build(BuildContext context) {
return Container(
@ -45,8 +73,8 @@ class InsuranceApprovalCard extends StatelessWidget {
children: [
AppCustomChipWidget(
labelText: isLoading ? "Approved" : insuranceApprovalResponseModel.approvalStatusDescption,
backgroundColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor.withOpacity(0.1) : AppColors.primaryRedColor.withOpacity(0.1),
textColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor : AppColors.primaryRedColor,
backgroundColor: isLoading ? AppColors.successColor.withValues(alpha: 0.1) : _getChipBackgroundColor(),
textColor: isLoading ? AppColors.successColor : _getChipTextColor(),
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
icon: isLoading ? AppAssets.walkin_appointment_icon : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon),
@ -69,7 +97,7 @@ class InsuranceApprovalCard extends StatelessWidget {
: appState.isArabic()
? insuranceApprovalResponseModel.isInOutPatientDescriptionN!
: insuranceApprovalResponseModel.isInOutPatientDescription!,
backgroundColor: AppColors.warningColorYellow.withOpacity(0.1),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow,
).toShimmer2(isShow: isLoading),
],

@ -350,7 +350,8 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
),
SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels
Row(
Directionality(
textDirection: ui.TextDirection.ltr, child:Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -378,7 +379,7 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
LocaleKeys.seconds.tr(context: context),
),
],
),
)),
SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon
CustomButton(

@ -79,7 +79,7 @@ class _RadiologyAiAnalysisDetailedPageState extends State<RadiologyAiAnalysisDet
ExpandableListItem(
backgroundColor: Colors.transparent,
expandedBackgroundColor: Colors.transparent,
title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
title: LocaleKeys.radiologyLabResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
Container(
@ -226,7 +226,7 @@ class _RadiologyAiAnalysisDetailedPageState extends State<RadiologyAiAnalysisDet
Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r),
SizedBox(width: 8.w),
Expanded(
child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}")
child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.disclaimerRadiology.tr()}")
.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400),
),
],

@ -80,7 +80,7 @@ class RadiologyAiAnalysisWidget extends StatelessWidget {
Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r),
SizedBox(width: 8.w),
Expanded(
child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400),
child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.disclaimerRadiology.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400),
),
],
),

@ -80,7 +80,7 @@ class _TriagePageState extends State<TriagePage> {
}
// Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered
if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 15) {
if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) {
// Navigate to results/possible conditions screen
context.navigateWithName(AppRoutes.possibleConditionsPage);
return;

@ -0,0 +1,185 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
class WeatherDetailsPage extends StatelessWidget {
const WeatherDetailsPage({super.key});
Color _getColorFromColorName(ColorName? colorName) {
if (colorName == null) return AppColors.successColor;
switch (colorName) {
case ColorName.GREEN:
return AppColors.successColor;
case ColorName.ORANGE:
return AppColors.warningColor;
case ColorName.RED:
return AppColors.errorColor;
}
}
Color _getBackgroundColorFromColorName(ColorName? colorName) {
if (colorName == null) return AppColors.successLightColor.withValues(alpha: 0.1);
switch (colorName) {
case ColorName.GREEN:
return AppColors.successLightColor.withValues(alpha: 0.1);
case ColorName.ORANGE:
return AppColors.warningLightColor.withValues(alpha: 0.1);
case ColorName.RED:
return AppColors.errorLightColor.withValues(alpha: 0.1);
}
}
IconData _getIconFromCategory(String? category) {
if (category == null) return Icons.wb_sunny_outlined;
final lowerCategory = category.toLowerCase();
if (lowerCategory.contains('walking') || lowerCategory.contains('activity')) {
return Icons.directions_walk;
} else if (lowerCategory.contains('weather') || lowerCategory.contains('temperature')) {
return Icons.thermostat_outlined;
} else if (lowerCategory.contains('elderly') || lowerCategory.contains('hiking')) {
return Icons.elderly;
}
return Icons.wb_sunny_outlined;
}
@override
Widget build(BuildContext context) {
return Consumer<WeatherMonitorViewModel>(
builder: (context, weatherVM, child) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.healthWeatherIndicators.tr(),
isLeading: true,
child: weatherVM.isLoading
? Center(
child: CircularProgressIndicator(
color: AppColors.primaryRedColor,
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Reuse WeatherWidget at the top (without the button)
const WeatherWidget(showButton: false),
SizedBox(height: 16.h),
// Categories List (Sliders)
...weatherVM.cityInfoList.map((cityInfo) {
return _buildCategoryCard(
description: cityInfo.name ?? '',
categoryLabel: cityInfo.category ?? '',
color: _getColorFromColorName(cityInfo.colorName),
backgroundColor: _getBackgroundColorFromColorName(cityInfo.colorName),
icon: _getIconFromCategory(cityInfo.category),
);
}),
SizedBox(height: 24.h),
],
),
),
);
},
);
}
Widget _buildCategoryCard({
required String description,
required String categoryLabel,
required Color color,
required Color backgroundColor,
required IconData icon,
}) {
return Container(
margin: EdgeInsets.only(bottom: 16.h),
padding: EdgeInsets.all(16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icon
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
icon,
color: color,
size: 24.w,
),
),
SizedBox(width: 12.w),
// Content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
description.toText14(
isBold: true,
color: AppColors.textColor,
),
SizedBox(height: 8.h),
// Progress bar
Container(
height: 4.h,
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(2.r),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: 0.7,
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(2.r),
),
),
),
),
],
),
),
SizedBox(width: 12.w),
// Category Label
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(8.r),
),
child: categoryLabel.toText12(
color: color,
fontWeight: FontWeight.w600,
maxLine: 1
),
),
],
),
).paddingSymmetrical(24.w, 0.w);
}
}

@ -79,10 +79,9 @@ class _SplashScreenState extends State<SplashPage> {
}
navigateToTeleConsult() async {
String roomID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID);
String callTypeID = await Utils.getStringFromPrefs(CacheConst.callTypeID);
print('RoomID: $roomID');
print('callTypeID: $callTypeID');
// GetIt.instance<CacheService>().remove(key: CacheConst.isAppOpenedFromCall);
Utils.removeFromPrefs(CacheConst.isAppOpenedFromCall);
@ -116,6 +115,18 @@ class _SplashScreenState extends State<SplashPage> {
print('actionCallAccept Event Received: ------->');
FlutterCallkitIncoming.endAllCalls();
try {
// Get callTypeID and roomID from event's extra data
String? eventCallTypeID = event.body['extra']?['callTypeID'];
String? eventRoomID = event.body['extra']?['roomID'];
// Re-save to SharedPreferences to ensure they're available
if (eventCallTypeID != null && eventCallTypeID.isNotEmpty) {
await Utils.saveStringFromPrefs(CacheConst.callTypeID, eventCallTypeID);
}
if (eventRoomID != null && eventRoomID.isNotEmpty) {
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, eventRoomID);
}
navigateToTeleConsult();
// String roomID = await Utils.getStringFromPrefs(SharedPrefsConsts.ZOOM_ROOM_ID);
// String roomPass = await Utils.getStringFromPrefs(SharedPrefsConsts.ZOOM_ROOM_PASS);

@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_kesy.g.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
@ -274,10 +274,10 @@ class TextInputWidget extends StatelessWidget {
final picked = await TimePickerWidget.show(
context,
initialTime: initialTime,
use24HourFormat: false, // You can make this configurable if needed
use24HourFormat: true,
onTimeSelected: (time) {
if (onChange != null) {
final formattedTime = TimePickerWidget.formatTime(time);
final formattedTime = TimePickerWidget.formatTime(time, use24HourFormat: true);
onChange!(formattedTime);
}
},
@ -285,7 +285,7 @@ class TextInputWidget extends StatelessWidget {
// Update controller if time was picked
if (picked != null && controller != null) {
controller!.text = TimePickerWidget.formatTime(picked);
controller!.text = TimePickerWidget.formatTime(picked, use24HourFormat: true);
}
},
child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.alarm_clock_icon),

@ -56,8 +56,7 @@ class _LanguageSelectorState extends State<LanguageSelector> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.language),
SizedBox(width: 6.h),
Text(
// currentLangData['name']?.toUpperCase() ?? 'EN',
widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية",
@ -69,6 +68,9 @@ class _LanguageSelectorState extends State<LanguageSelector> {
isLanguageSwitcher: true,
fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "CairoArabic"),
),
SizedBox(width: 6.h),
Utils.buildSvgWithAssets(icon: AppAssets.language),
],
),
),

@ -1,3 +1,5 @@
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -126,48 +128,50 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
return Directionality(
textDirection: ui.TextDirection.ltr,
child: Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
),
),
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.dividerColor,
width: 1,
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.dividerColor,
width: 1,
),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.selectTime.tr(context: context).toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.selectTime.tr(context: context).toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Utils.buildSvgWithAssets(
icon: AppAssets.cancel,
width: 24.h,
height: 24.h,
iconColor: AppColors.textColor,
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Utils.buildSvgWithAssets(
icon: AppAssets.cancel,
width: 24.h,
height: 24.h,
iconColor: AppColors.textColor,
),
),
],
],
),
),
),
// Time Picker
SizedBox(
@ -345,6 +349,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> {
],
),
),
),
);
}
}

Loading…
Cancel
Save