Vital sign data connected to Symptom checker

haroon_dev
Haroon Amjad 16 hours ago
parent eeaa22357f
commit 4ce1e912ce

@ -683,7 +683,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

@ -487,7 +487,9 @@ class DateUtil {
}
static String getFormattedDate(DateTime dateTime, String formattedString) {
return DateFormat(formattedString).format(dateTime);
String formattedDate = DateFormat(formattedString, "en-US").format(dateTime);
print(formattedDate);
return formattedDate;
}
static convertISODateToJsonDate(String isoDate) {

@ -258,6 +258,7 @@ extension EmailValidator on String {
this,
maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null,
// locale: Locale('en', 'US'),
style: TextStyle(
color: color ?? AppColors.blackColor,
fontSize: 16.f,

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.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/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -31,8 +33,7 @@ class HmgServicesViewModel extends ChangeNotifier {
final ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
HmgServicesViewModel(
{required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService});
HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService});
bool isCmcOrdersLoading = false;
bool isCmcServicesLoading = false;
@ -55,9 +56,11 @@ class HmgServicesViewModel extends ChangeNotifier {
// Vital Sign PageView Controller
PageController _vitalSignPageController = PageController();
PageController get vitalSignPageController => _vitalSignPageController;
int _vitalSignCurrentPage = 0;
int get vitalSignCurrentPage => _vitalSignCurrentPage;
void setVitalSignCurrentPage(int page) {
@ -65,7 +68,6 @@ class HmgServicesViewModel extends ChangeNotifier {
notifyListeners();
}
// HHC specific lists
List<GetCMCAllOrdersResponseModel> hhcOrdersList = [];
List<GetCMCServicesResponseModel> hhcServicesList = [];
@ -82,9 +84,8 @@ class HmgServicesViewModel extends ChangeNotifier {
List<SearchEReferralResponseModel> searchReferralList = [];
List<Covid19GetTestProceduresResp> covidTestProcedureList = [];
Covid19GetPaymentInfo? covidPaymentInfo;
Future<void> getOrdersList() async {}
Future<void> getOrdersList() async {}
// HHC multiple services selection
List<GetCMCServicesResponseModel> selectedHhcServices = [];
@ -792,22 +793,20 @@ class HmgServicesViewModel extends ChangeNotifier {
},
);
}
List<CovidQuestionnaireModel> getQuestionsFromJson() {
final String questionsJson = '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]''';
final String questionsJson =
'''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]''';
try {
final parsed = json.decode(questionsJson) as List<dynamic>;
return parsed
.map((e) => CovidQuestionnaireModel.fromJson(Map<String, dynamic>.from(e)))
.toList();
return parsed.map((e) => CovidQuestionnaireModel.fromJson(Map<String, dynamic>.from(e))).toList();
} catch (_) {
return <CovidQuestionnaireModel>[];
}
}
}
Future<void> getCovidProcedureList({
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
@ -816,14 +815,14 @@ class HmgServicesViewModel extends ChangeNotifier {
final result = await hmgServicesRepo.getCovidTestProcedures();
result.fold(
(failure) async {
(failure) async {
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
(apiResponse) {
if (apiResponse.messageStatus == 1) {
covidTestProcedureList = apiResponse.data ?? [];
notifyListeners();
@ -840,7 +839,6 @@ class HmgServicesViewModel extends ChangeNotifier {
);
}
Future<void> getPaymentInfo({
String? procedureID,
int? projectID,
@ -852,14 +850,14 @@ class HmgServicesViewModel extends ChangeNotifier {
final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!);
result.fold(
(failure) async {
(failure) async {
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
(apiResponse) {
if (apiResponse.messageStatus == 1) {
covidPaymentInfo = apiResponse.data;
notifyListeners();
@ -900,6 +898,8 @@ class HmgServicesViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1) {
vitalSignList = apiResponse.data ?? [];
hasVitalSignDataLoaded = true;
getIt.get<SymptomsCheckerViewModel>().setSelectedHeight(vitalSignList.first.heightCm);
getIt.get<SymptomsCheckerViewModel>().setSelectedWeight(vitalSignList.first.weightKg);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -84,9 +84,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? _selectedGender;
DateTime? _dateOfBirth;
int? _selectedAge;
double _selectedHeight = 170;
num _selectedHeight = 170;
bool _isHeightCm = true;
double _selectedWeight = 60;
num _selectedWeight = 60;
bool _isWeightKg = true;
// Getters
@ -108,11 +108,21 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_selectedAge = age;
}
double? get selectedHeight => _selectedHeight;
setSelectedHeight(num height) {
_selectedHeight = height;
notifyListeners();
}
setSelectedWeight(num weight) {
_selectedWeight = weight;
notifyListeners();
}
num? get selectedHeight => _selectedHeight;
bool get isHeightCm => _isHeightCm;
double? get selectedWeight => _selectedWeight;
num? get selectedWeight => _selectedWeight;
bool get isWeightKg => _isWeightKg;

@ -73,6 +73,7 @@ class LoginScreenState extends State<LoginScreen> {
SizedBox(height: 130.h), // 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
TextInputWidget(
labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}",
hintText: "xxxxxxxxx",

@ -1579,6 +1579,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
weight: FontWeight.w600,
),
),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h),
],
),
SizedBox(height: 14.h),

@ -9,6 +9,7 @@ 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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -39,13 +40,18 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
if (appState.isAuthenticated) {
final user = appState.getAuthenticatedUser();
getIt.get<HmgServicesViewModel>().getPatientVitalSign();
if (user == null) return;
// Populate gender (gender is int: 1=Male, 2=Female)
// Use internal keys (male/female) for storage
if (user.gender != null) {
String genderKey = user.gender == 1 ? "male" : user.gender == 2 ? "female" : "other";
String genderKey = user.gender == 1
? "male"
: user.gender == 2
? "female"
: "other";
viewModel.setGender(genderKey);
}
@ -64,13 +70,14 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
// If not authenticated or fields are empty, user will fill them manually
}
_buildEditInfoTile({
Widget _buildEditInfoTile({
required String leadingIcon,
required String title,
required String subTitle,
required VoidCallback onTap,
required String trailingIcon,
required BuildContext context,
required HmgServicesViewModel hmgServicesVM,
Color? iconColor,
}) {
return InkWell(
@ -92,7 +99,9 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText14(weight: FontWeight.w500),
subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
subTitle
.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500)
.toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
],
),
],
@ -155,25 +164,20 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, child) {
body: Consumer2<SymptomsCheckerViewModel, HmgServicesViewModel>(
builder: (context, viewModel, hmgServicesVM, child) {
// Check if any field is empty
bool hasEmptyFields = viewModel.selectedGender == null ||
viewModel.selectedAge == null ||
viewModel.selectedHeight == null ||
viewModel.selectedWeight == null;
bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null;
// Get display values
String genderText = _getLocalizedGender(viewModel.selectedGender, context);
// Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB)
int? displayAge = viewModel.selectedAge ?? userAgeFromDOB;
String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context);
String heightText = viewModel.selectedHeight != null
? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}"
: LocaleKeys.notSet.tr(context: context);
String weightText = viewModel.selectedWeight != null
? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}"
: LocaleKeys.notSet.tr(context: context);
String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context);
String weightText = viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context);
return Column(
children: [
@ -196,53 +200,53 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.genderIcon,
title: LocaleKeys.gender.tr(context: context),
subTitle: genderText,
onTap: () {
viewModel.setUserInfoPage(0, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.genderIcon,
title: LocaleKeys.gender.tr(context: context),
subTitle: genderText,
onTap: () {
viewModel.setUserInfoPage(0, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.calendarGrey,
title: LocaleKeys.age.tr(context: context),
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
viewModel.setUserInfoPage(1, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.calendarGrey,
title: LocaleKeys.age.tr(context: context),
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
viewModel.setUserInfoPage(1, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.rulerIcon,
title: LocaleKeys.height.tr(context: context),
subTitle: heightText,
onTap: () {
viewModel.setUserInfoPage(2, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.rulerIcon,
title: LocaleKeys.height.tr(context: context),
subTitle: heightText,
onTap: () {
viewModel.setUserInfoPage(2, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.weightScale,
title: LocaleKeys.weight.tr(context: context),
subTitle: weightText,
onTap: () {
viewModel.setUserInfoPage(3, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.weightScale,
title: LocaleKeys.weight.tr(context: context),
subTitle: weightText,
onTap: () {
viewModel.setUserInfoPage(3, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
],
),
),
@ -292,6 +296,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
text: LocaleKeys.yesItIs.tr(context: context),
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
isDisabled: getIt.get<HmgServicesViewModel>().isVitalSignLoading || hasEmptyFields,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),

@ -158,7 +158,7 @@ class HeightSelectionPage extends StatelessWidget {
enableSound: true,
minValue: minValue,
maxValue: maxValue,
initialHeight: viewModel.selectedHeight ?? 100,
initialHeight: (viewModel.selectedHeight ?? 100).toDouble(),
isCm: viewModel.isHeightCm,
onHeightChanged: (newHeight) {
log("height: $newHeight");

@ -150,7 +150,7 @@ class WeightSelectionPage extends StatelessWidget {
enableSound: true,
minValue: minValue,
maxValue: maxValue,
initialWeight: viewModel.selectedWeight!,
initialWeight: (viewModel.selectedWeight ?? 70).toDouble(),
isKg: isKg,
onWeightChanged: (newWeight) {
log("weight: $newWeight");

@ -3,6 +3,8 @@ import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -330,6 +332,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
weight: FontWeight.w600,
),
),
Utils.buildSvgWithAssets(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward,
width: 18.w, height: 18.h),
],
),
SizedBox(height: 14.h),

@ -301,6 +301,7 @@ class TextInputWidget extends StatelessWidget {
Widget _buildTextField(BuildContext context) {
double fontS = fontSize ?? 14.f;
return TextField(
hintLocales: const [Locale('en', 'US')],
enabled: isEnable,
scrollPadding: EdgeInsets.zero,
keyboardType: isMultiline ? TextInputType.multiline : keyboardType,

Loading…
Cancel
Save