completed suggested changes

faiz_dev_symptoms_checker
faizatflutter 2 days ago
parent 1587dddda4
commit fb75e326d2

@ -1443,8 +1443,8 @@
"pleaseSelectAtLeastOneOptionBeforeProceeding": "يرجى اختيار خيار واحد على الأقل قبل المتابعة",
"suggestions": "التوصيات",
"pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً",
"symptomsSelector": "محدد الأعراض",
"possibleSymptomsRelatedTo": "أعراض مرتبطة بـ",
"symptomsSelector": "محدد الأعراض",
"possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بأعضاء محددة",
"suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة",
"whyAmIBeingAskedThis": "لماذا يُطرح علي هذا السؤال؟",
"weAreAskingThisQuestionToBetterUnderstand": "نطرح هذا السؤال لفهم {symptoms} بشكل أفضل وتحسين دقة التقييم.",
@ -1856,6 +1856,7 @@
"watchUsOnYoutube": "شاهدنا على يوتيوب",
"connectOnLinkedin": "تواصل معنا على لينكد إن",
"riskFactorPageTitle": "اعراض تشعر بها",
"doAnyOfTheseApply": "هل ينطبق عليك أي من هذه الأمور؟",
"bloodDonationService": "التبرع \n بالدم",
"bookAppointmentService": "حجز\nموعد",
"pinchToZoom": "اضغط للتكبير",

@ -1436,7 +1436,7 @@
"suggestions": "Suggestions",
"pleaseGoBackAndSelectOrgansFirst": "Please go back and select organs first",
"symptomsSelector": "Symptoms Selector",
"possibleSymptomsRelatedTo": "Possible symptoms related to",
"possibleSymptomsRelatedTo": "Possible symptoms related to selected organs",
"suggestionAIListDescription": "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview",
"whyAmIBeingAskedThis": "Why am I being asked this?",
"weAreAskingThisQuestionToBetterUnderstand": "We are asking this question to better understand your {symptoms} and improve the accuracy of the assessment.",
@ -1848,6 +1848,7 @@
"watchUsOnYoutube": "Watch us on youtube",
"connectOnLinkedin": "Connect on LinkedIn",
"riskFactorPageTitle": "What do you feel?",
"doAnyOfTheseApply": "Do any of these apply to you?",
"bloodDonationService": "Blood \nDonation",
"bookAppointmentService": "Book\nAppointment",
"pinchToZoom": "Pinch to zoom",

@ -105,20 +105,14 @@ class TriageQuestion {
factory TriageQuestion.fromJson(Map<String, dynamic> json) {
// TESTING: Add dummy instructions to every question for testing UI
final dummyInstructions = [
"Clean the thermometer using soap and cold water.",
"For rectal insertion, apply a small amount of petroleum jelly to the end of the thermometer.",
"Insert the silver tip into the anus or mouth.",
"Hold the thermometer in place for 2 minutes, or as long as recommended by the manufacturer.",
];
final dummyExplication = "The most accurate measurement is rectal temperature. You can also measure the temperature under the armpit or use contactless thermometers. However, using the contact thermometer is more accurate. Follow the thermometer manufacturer's instructions. A temperature above 37°C or 98.6°F is classified as a fever.";
return TriageQuestion(
type: json['type'],
text: json['text'],
explication: json['explication'] ?? dummyExplication, // Use dummy if null
instruction: json['instruction'] != null ? List<String>.from(json['instruction']) : dummyInstructions, // Use dummy if null
explication: json['explication'],
// Use dummy if null
instruction: json['instruction'] != null ? List<String>.from(json['instruction']) : [],
// Use dummy if null
items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null,
);
}

@ -2,7 +2,7 @@
// ignore_for_file: constant_identifier_names
abstract class LocaleKeys {
abstract class LocaleKeys {
static const english = 'english';
static const arabic = 'arabic';
static const login = 'login';
@ -1808,6 +1808,7 @@ abstract class LocaleKeys {
static const watchUsOnYoutube = 'watchUsOnYoutube';
static const connectOnLinkedin = 'connectOnLinkedin';
static const riskFactorPageTitle = 'riskFactorPageTitle';
static const doAnyOfTheseApply = 'doAnyOfTheseApply';
static const bloodDonationService = 'bloodDonationService';
static const bookAppointmentService = 'bookAppointmentService';
static const pinchToZoom = 'pinchToZoom';
@ -1883,5 +1884,4 @@ abstract class LocaleKeys {
static const advanceNumber = 'advanceNumber';
static const amountOnly = 'amountOnly';
static const invoiceHistory = 'invoiceHistory';
}

@ -1847,6 +1847,7 @@ abstract class LocaleKeys {
static const watchUsOnYoutube = 'watchUsOnYoutube';
static const connectOnLinkedin = 'connectOnLinkedin';
static const riskFactorPageTitle = 'riskFactorPageTitle';
static const doAnyOfTheseApply = 'doAnyOfTheseApply';
static const bloodDonationService = 'bloodDonationService';
static const bookAppointmentService = 'bookAppointmentService';
static const pinchToZoom = 'pinchToZoom';

@ -71,16 +71,21 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
);
return;
}
// If user is not authenticated, navigate to user consent screen
if (!_appState.isAuthenticated) {
context.navigateWithName(AppRoutes.userConsentScreen);
return;
}
// User is authenticated, proceed with API call
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.pleaseWait.tr(context: context),
);
// Get fileNo if user is logged in
String? fileNo;
if (_appState.isAuthenticated) {
final user = _appState.getAuthenticatedUser();
fileNo = user?.patientId.toString();
}
// Get fileNo from authenticated user
final user = _appState.getAuthenticatedUser();
String? fileNo = user?.patientId.toString();
await viewModel.getSymptomsUserDetails(
userName: ApiConsts.symptomsCheckerUsername,

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -31,7 +29,6 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class PossibleConditionsPage extends StatefulWidget {
const PossibleConditionsPage({super.key});
@ -91,25 +88,81 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
}
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(16.w),
itemCount: 10,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 80.h,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Triage Level Card Shimmer
// Condition Cards Shimmer
...List.generate(5, (index) {
return Container(
width: double.infinity,
margin: EdgeInsets.only(left: 24.w, right: 24.w, bottom: 16.h),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(10.r),
borderRadius: BorderRadius.circular(24.r),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10.r,
offset: Offset(0, 2.h),
),
],
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Condition name shimmer
Container(
height: 20.h,
width: 220.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
).toShimmer2(isShow: true, radius: 8.r),
SizedBox(height: 8.h),
Container(
height: 20.h,
width: 200.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
).toShimmer2(isShow: true, radius: 8.r),
SizedBox(height: 8.h),
// Probability percentage shimmer
Container(
height: 16.h,
width: 100.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
).toShimmer2(isShow: true, radius: 8.r),
SizedBox(height: 30.h),
// Action buttons shimmer
Row(
children: [
Expanded(
child: Container(
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(12.r),
),
).toShimmer2(isShow: true, radius: 12.r),
),
],
),
],
),
);
}),
],
);
}
@ -323,7 +376,10 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
trailing: _buildTrailingSection(context),
child: Consumer<SymptomsCheckerViewModel>(
builder: (context, symptomsCheckerViewModel, child) {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isTriageDiagnosisLoading) {
// Show shimmer while loading triage level or conditions
if (symptomsCheckerViewModel.isPossibleConditionsLoading ||
symptomsCheckerViewModel.isTriageDiagnosisLoading ||
symptomsCheckerViewModel.isTriageLevelLoading) {
return _buildLoadingShimmer();
}
// Get conditions directly from ViewModel
@ -350,9 +406,7 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
// Show error if no clinic details available
final dialogService = getIt<DialogService>();
dialogService.showErrorBottomSheet(
message: appState.isArabic()
? "لا توجد تفاصيل عيادة متاحة"
: "No clinic details available",
message: appState.isArabic() ? "لا توجد تفاصيل عيادة متاحة" : "No clinic details available",
);
return;
}
@ -451,13 +505,12 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
var id = "";
if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
// Check if hospitalList is not empty before accessing first
if (regionalViewModel.selectedHospital?.hospitalList != null &&
regionalViewModel.selectedHospital!.hospitalList.isNotEmpty) {
if (regionalViewModel.selectedHospital?.hospitalList != null && regionalViewModel.selectedHospital!.hospitalList.isNotEmpty) {
id = regionalViewModel.selectedHospital!.hospitalList.first.iD?.toString() ?? "";
}
} else {
// Check if patientDoctorAppointmentList is not empty before accessing first
if (regionalViewModel.selectedHospital?.patientDoctorAppointmentList != null &&
if (regionalViewModel.selectedHospital?.patientDoctorAppointmentList != null &&
regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.isNotEmpty) {
id = regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.first.projectID?.toString() ?? "";
}

@ -1,5 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
@ -121,12 +120,11 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
child: RichText(
text: TextSpan(
style: TextStyle(
height: 1.3,
fontSize: 13.f,
height: 1.3,
fontSize: 13.f,
fontWeight: FontWeight.w600,
color: AppColors.greyInfoTextColor,
fontFamily: isArabic ? 'CairoArabic' : 'Poppins'
),
color: AppColors.greyInfoTextColor,
fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
children: [
TextSpan(
text: LocaleKeys.aboveYouSeeCommonRiskFactors.tr(context: context),
@ -223,7 +221,7 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.riskFactorPageTitle.tr(context: context),
title: LocaleKeys.doAnyOfTheseApply.tr(context: context),
leadingCallback: () => context.pop(),
child: viewModel.isRiskFactorsLoading
? _buildLoadingShimmer()

@ -200,7 +200,7 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.symptomsCheckSuggestions.tr() ,
title: LocaleKeys.riskFactorPageTitle.tr() ,
leadingCallback: () => context.pop(),
child: viewModel.isSuggestionsLoading
? _buildLoadingShimmer()

@ -225,7 +225,7 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
children: [
Expanded(
child: Text(
'${LocaleKeys.possibleSymptomsRelatedTo.tr(context: context)} "$organName"',
LocaleKeys.possibleSymptomsRelatedTo.tr(context: context),
style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
),
),

@ -82,7 +82,7 @@ class _TriagePageState extends State<TriagePage> {
// Case 3: Probability >= 70% OR
// Case 4: 30 or more questions answered
// if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) {
if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30 || viewModel.shouldStopTriage) {
if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 4 || viewModel.shouldStopTriage) {
// Navigate to results/possible conditions screen
context.navigateWithName(AppRoutes.possibleConditionsPage);
return;
@ -346,7 +346,7 @@ class _TriagePageState extends State<TriagePage> {
final hasInstructions = instructions.isNotEmpty;
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.howTo.tr(context: context),
title: LocaleKeys.information.tr(context: context),
context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -8,10 +8,13 @@ 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/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';
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/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
// Before using the checkup, please read the Terms of Service and remember:
//
@ -43,6 +46,35 @@ class _UserConsentScreenState extends State<UserConsentScreen> {
bool get _canProceed => _isTermsAccepted && _isPrivacyAccepted;
void _onNextPressed() async {
final viewModel = context.read<SymptomsCheckerViewModel>();
final appState = getIt.get<AppState>();
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.pleaseWait.tr(context: context),
);
// For non-authenticated users, fileNo is null
String? fileNo;
if (appState.isAuthenticated) {
final user = appState.getAuthenticatedUser();
fileNo = user?.patientId.toString();
}
await viewModel.getSymptomsUserDetails(
userName: ApiConsts.symptomsCheckerUsername,
password: ApiConsts.symptomsCheckerPassword,
fileNo: fileNo,
onSuccess: () {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.symptomsSelectorPage);
},
onError: (String error) {
LoaderBottomSheet.hideLoader();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -213,7 +245,7 @@ class _UserConsentScreenState extends State<UserConsentScreen> {
Expanded(
child: CustomButton(
text: LocaleKeys.next.tr(context: context),
onPressed: _canProceed ? () => context.navigateWithName(AppRoutes.userInfoSelection) : null,
onPressed: _canProceed ? _onNextPressed : null,
isDisabled: !_canProceed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -71,8 +71,8 @@ dependencies:
firebase_analytics: ^11.5.1
firebase_crashlytics: ^4.3.8
jiffy: ^6.4.3
# hijri_gregorian_calendar:
# path: /Users/aamir/StudioProjects/hijri_gregorian_calendar
# hijri_gregorian_calendar:
# path: /Users/aamir/StudioProjects/hijri_gregorian_calendar
sms_otp_auto_verify: ^2.2.0
web: any
flutter_staggered_animations: ^1.1.1
@ -89,7 +89,7 @@ dependencies:
path: package/flutter_swiper_view
flutter_callkit_incoming: ^3.0.0
in_app_update: ^4.2.5
# device_preview: ^1.3.1
# device_preview: ^1.3.1
image_picker_android: ^0.8.13+16
image_picker_platform_interface: ^2.10.0

Loading…
Cancel
Save