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

Reviewed-on: #296
haroon_dev
khansultan1 16 hours ago
commit 467b73ef7f

@ -1839,5 +1839,6 @@
"generateAiAnalysisPrescription": "تحليل الذكاء الاصطناعي",
"loadingAIAnalysisPrescription": "ٍتحليل الوصفات الطبية بواسطة الذكاء الاصطناعي، يرجى الانتظار...",
"thisAboveInfoPrescription": "تم تحليل الوصفة الطبية هذه بواسطة الذكاء الاصطناعي، وهي لا تُعدّ نصيحة طبية. استشر طبيبك المختص للتشخيص والعلاج.",
"liveCareNotificationPermissionsMessage": "يتطلب لايف كير أذونات الإشعارات، يرجى السماح بهذه الأذونات للمتابعة."
"liveCareNotificationPermissionsMessage": "يتطلب لايف كير أذونات الإشعارات، يرجى السماح بهذه الأذونات للمتابعة.",
"weatherIndicators": "طقس"
}

@ -1829,7 +1829,8 @@
"generateAiAnalysisPrescription": "Generate AI analysis",
"loadingAIAnalysisPrescription": "Analysing your prescription, let the AI do the magic, This might take some time.",
"thisAboveInfoPrescription": "This prescription was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.",
"liveCareNotificationPermissionsMessage": "LiveCare requires Notifications permission, Please allow to proceed."
"liveCareNotificationPermissionsMessage": "LiveCare requires Notifications permission, Please allow to proceed.",
"weatherIndicators": "Weather"
}

@ -234,6 +234,8 @@ class AppAssets {
static const String add_new_family_icon = '$svgBasePath/add_new_family_icon.svg';
static const String h_calc = '$svgBasePath/h_calc.svg';
static const String h_calc_selected = '$svgBasePath/h_calc_selected.svg';
static const String weatherBottom = '$svgBasePath/weather_bottom.svg';
static const String weatherBottomFill = '$svgBasePath/weather_bottom_fill.svg';
static const String height = '$svgBasePath/height.svg';
static const String weight = '$svgBasePath/weight.svg';

@ -30,6 +30,42 @@ class WeatherMonitorViewModel extends ChangeNotifier {
isLoading = true;
notifyListeners();
_cityInfoList.clear();
// Check if location permission is already granted
final isLocationEnabled = await locationUtils.isLocationPermissionEnabled();
if (isLocationEnabled) {
// Permission already granted, fetch data directly
isLocationAvailable = true;
final result = await weatherRepo.getCityInfo();
result.fold(
(failure) async {
isLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isLoading = false;
if (apiResponse.messageStatus == 2) {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
final cities = apiResponse.data ?? [];
_cityInfoList.addAll(cities);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
} else {
// Permission not granted, request it
locationUtils.isShowConfirmDialog = true;
locationUtils.getLocation(
isShowConfirmDialog: true,
@ -85,6 +121,7 @@ class WeatherMonitorViewModel extends ChangeNotifier {
);
});
}
}
Future<bool> checkIfTheLocationIsEnabledOrNot() async {
bool value = await locationUtils.isLocationPermissionEnabled();

@ -1831,6 +1831,7 @@ abstract class LocaleKeys {
static const generateAiAnalysisPrescription = 'generateAiAnalysisPrescription';
static const loadingAIAnalysisPrescription = 'loadingAIAnalysisPrescription';
static const thisAboveInfoPrescription = 'thisAboveInfoPrescription';
static const weatherIndicators = 'weatherIndicators';
static const liveCareNotificationPermissionsMessage = 'liveCareNotificationPermissionsMessage';
}

@ -1,19 +1,23 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/core/enums.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/weather/weather_details_page.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:permission_handler/permission_handler.dart' show openAppSettings;
import '../health_calculators_and_converts/health_calculators_page.dart';
class LandingNavigation extends StatefulWidget {
const LandingNavigation({super.key});
@ -35,7 +39,7 @@ class _LandingNavigationState extends State<LandingNavigation> {
physics: const NeverScrollableScrollPhysics(),
children: [
const LandingPage(),
appState.isAuthenticated ? MedicalFilePage(showBackIcon: false) : /* need add feedback page */ HealthCalculatorsPage(type: HealthCalConEnum.calculator),
appState.isAuthenticated ? MedicalFilePage(showBackIcon: false) : const WeatherDetailsPage(showBackIcon: false,),
SizedBox(),
// const ToDoPage(),
// appState.isAuthenticated ? UserInfoSelectionScreen() : /* need add news page */ SizedBox(),
@ -45,8 +49,47 @@ class _LandingNavigationState extends State<LandingNavigation> {
),
bottomNavigationBar: BottomNavigation(
currentIndex: _currentIndex,
onTap: (index) {
onTap: (index) async {
setState(() => _currentIndex = index);
if (_currentIndex == 1 && !appState.isAuthenticated) {
final locationUtils = getIt.get<LocationUtils>();
final isLocationEnabled = await locationUtils.isLocationPermissionEnabled();
if (isLocationEnabled) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
} else {
locationUtils.getLocation(
isShowConfirmDialog: true,
onSuccess: (position) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
},
onFailure: () {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.grantLocationPermission.tr(),
isShowActionButtons: true,
onCancelTap: () {
getIt.get<NavigationService>().pop();
setState(() => _currentIndex = 0);
},
onConfirmTap: () async {
getIt.get<NavigationService>().pop();
openAppSettings();
setState(() => _currentIndex = 0);
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}
return;
}
if (_currentIndex == 2) {
getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();

@ -10,9 +10,29 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_wi
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';
import 'package:shimmer/shimmer.dart';
class WeatherDetailsPage extends StatelessWidget {
const WeatherDetailsPage({super.key});
class WeatherDetailsPage extends StatefulWidget {
final bool showBackIcon;
const WeatherDetailsPage({super.key, this.showBackIcon = true});
@override
State<WeatherDetailsPage> createState() => _WeatherDetailsPageState();
}
class _WeatherDetailsPageState extends State<WeatherDetailsPage> {
@override
void initState() {
super.initState();
// Fetch weather data when page loads only if data is not already available
WidgetsBinding.instance.addPostFrameCallback((_) {
final weatherVM = Provider.of<WeatherMonitorViewModel>(context, listen: false);
// Only fetch if cityInfoList is empty
if (weatherVM.cityInfoList.isEmpty && !weatherVM.isLoading) {
weatherVM.initiateFetchWeather();
}
});
}
Color _getColorFromColorName(ColorName? colorName) {
if (colorName == null) return AppColors.successColor;
@ -132,13 +152,9 @@ class WeatherDetailsPage extends StatelessWidget {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.healthWeatherIndicators.tr(),
isLeading: true,
isLeading: widget.showBackIcon,
child: weatherVM.isLoading
? Center(
child: CircularProgressIndicator(
color: AppColors.primaryRedColor,
),
)
? _buildShimmerLoading()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -251,5 +267,164 @@ class WeatherDetailsPage extends StatelessWidget {
),
).paddingSymmetrical(24.w, 0.w);
}
Widget _buildShimmerLoading() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Weather Widget Shimmer
Shimmer.fromColors(
baseColor: AppColors.shimmerBaseColor,
highlightColor: AppColors.shimmerHighlightColor,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 64.w,
height: 64.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
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.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
SizedBox(height: 8.h),
Container(
width: 120.w,
height: 16.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
SizedBox(height: 8.h),
Container(
width: 160.w,
height: 12.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
],
),
),
],
),
SizedBox(height: 16.h),
Container(
width: double.infinity,
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(10.r),
),
),
],
),
),
),
SizedBox(height: 16.h),
// Shimmer for category cards
...List.generate(3, (index) {
return Shimmer.fromColors(
baseColor: AppColors.shimmerBaseColor,
highlightColor: AppColors.shimmerHighlightColor,
child: Container(
margin: EdgeInsets.only(bottom: 16.h, left: 24.w, right: 24.w),
padding: EdgeInsets.all(16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle,
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
height: 14.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
SizedBox(height: 8.h),
Container(
width: 100.w,
height: 12.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
],
),
),
Container(
width: 60.w,
height: 24.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
),
],
),
SizedBox(height: 12.h),
Container(
width: double.infinity,
height: 8.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(4.r),
),
),
],
),
),
);
}),
],
);
}
}

@ -33,6 +33,7 @@ import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_scr
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/presentation/weather/weather_details_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
import 'package:provider/provider.dart';
@ -95,6 +96,9 @@ class AppRoutes {
// Services Price List
static const String servicesPriceListPage = '/servicesPriceListPage';
// Weather
static const String weatherDetailsPage = '/weatherDetailsPage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
@ -131,6 +135,7 @@ class AppRoutes {
),
emergencyServicesPage: (context) => EmergencyServicesPage(),
servicesPriceListPage: (context) => ServicesPriceListPage(),
weatherDetailsPage: (context) => WeatherDetailsPage(),
addHealthTrackerEntryPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return AddHealthTrackerEntryPage(

@ -23,7 +23,7 @@ class BottomNavigation extends StatelessWidget {
BottomNavItem(icon: AppAssets.homeBottom, fillIcon: AppAssets.homeBottomFill, label: LocaleKeys.home.tr(context: context)),
appState.isAuthenticated
? BottomNavItem(icon: AppAssets.myFilesBottom, fillIcon: AppAssets.myFilesBottomFill, label: LocaleKeys.medicalFile.tr(context: context))
: BottomNavItem(icon: AppAssets.h_calc, fillIcon: AppAssets.h_calc_selected, label: LocaleKeys.healthTools.tr()),
: BottomNavItem(icon: AppAssets.weatherBottom, fillIcon: AppAssets.weatherBottomFill, label: LocaleKeys.weatherIndicators.tr(context: context)),
BottomNavItem(
icon: AppAssets.bookAppoBottom,
fillIcon: AppAssets.bookAppoBottom,

Loading…
Cancel
Save