no message

dev_sultan
Sultan khan 1 day ago
parent d07038298f
commit a2645f7309

@ -369,20 +369,23 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
void filterClinics(String? query) {
if (query!.isEmpty) {
if (query == null || query.trim().isEmpty) {
_filteredClinicsList = List.from(clinicsList);
showSortFilterButtons = false;
} else {
// Normalize the search query for better Arabic text matching
String normalizedQuery = Utils.normalizeArabicText(query.toLowerCase());
String normalizedQuery = Utils.normalizeArabicText(query.trim().toLowerCase());
_filteredClinicsList = clinicsList.where((clinic) {
String clinicName = clinic.clinicDescription?.toLowerCase() ?? '';
String clinicNameN = clinic.clinicDescriptionN?.toLowerCase() ?? '';
String normalizedClinicName = Utils.normalizeArabicText(clinicName);
return normalizedClinicName.contains(normalizedQuery);
String normalizedClinicNameN = Utils.normalizeArabicText(clinicNameN);
return normalizedClinicName.contains(normalizedQuery) ||
normalizedClinicNameN.contains(normalizedQuery);
}).toList();
showSortFilterButtons = query.length >= 3;
showSortFilterButtons = query.trim().length >= 3;
}
notifyListeners();
}

@ -115,44 +115,52 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: bookAppointmentsViewModel.isLiveCareSchedule ? LocaleKeys.selectLiveCareClinic.tr(context: context) : LocaleKeys.selectClinic.tr(context: context),
stickyWidgetHeight: 56.h,
stickyWidget: Padding(
padding: EdgeInsets.fromLTRB(24.h, 0, 24.h, 14.h),
child: Consumer<BookAppointmentsViewModel>(
builder: (context, _, __) {
return TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.clinicName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
);
},
),
),
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
return Column(
children: [
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.clinicName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
bookAppointmentsVM.isLiveCareSchedule
? ListView.separated(
padding: EdgeInsets.only(top: 24.h),
padding: EdgeInsets.only(top: 12.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isClinicsListLoading
@ -199,7 +207,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
)
: ListView.separated(
padding: EdgeInsets.only(top: 24.h),
padding: EdgeInsets.only(top: 12.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length,

@ -60,6 +60,7 @@ import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.da
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_details_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -429,13 +430,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
},
children: _buildVitalSignPages(
vitalSign: hmgServicesVM.vitalSignList.first,
onTap: () {
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignPage(),
),
);
},
currentPageIndex: hmgServicesVM.vitalSignCurrentPage,
),
),
@ -1496,10 +1490,20 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}
void _openVitalSignDetails(VitalSignDetailsArgs args, VitalSignResModel? targetVitalSign) {
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignDetailsPage(
args: args,
selectedVitalSign: targetVitalSign,
),
),
);
}
// Build pages with 2 cards each
List<Widget> _buildVitalSignPages({
required VitalSignResModel vitalSign,
required VoidCallback onTap,
required int currentPageIndex,
}) {
return [
@ -1515,8 +1519,15 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
value: _formatValue(vitalSign.bodyMassIndex),
unit: '',
status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null,
// status: "Overweight",
onTap: onTap,
onTap: () => _openVitalSignDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.bmi,
title: LocaleKeys.bmi.tr(context: context),
icon: AppAssets.bmiVital,
unit: '',
),
vitalSign,
),
),
),
SizedBox(width: 12.w),
@ -1527,7 +1538,15 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
value: _formatValue(vitalSign.heightCm),
unit: LocaleKeys.cm.tr(),
status: null,
onTap: onTap,
onTap: () => _openVitalSignDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.height,
title: LocaleKeys.height.tr(context: context),
icon: AppAssets.heightVital,
unit: 'cm',
),
vitalSign,
),
),
),
],
@ -1535,39 +1554,64 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
// Page 2: Weight + Blood Pressure
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Row(
children: [
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.weightVital,
label: LocaleKeys.weight.tr(context: context),
value: _formatValue(vitalSign.weightKg),
unit: LocaleKeys.kg.tr(),
status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null,
onTap: onTap,
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Row(
children: [
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.weightVital,
label: LocaleKeys.weight.tr(context: context),
value: _formatValue(vitalSign.weightKg),
unit: LocaleKeys.kg.tr(),
status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null,
onTap: () => _openVitalSignDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.weight,
title: LocaleKeys.weight.tr(context: context),
icon: AppAssets.weightVital,
unit: 'kg',
),
vitalSign,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: LocaleKeys.bloodPressure.tr(context: context),
value: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
: '--',
unit: '',
status: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
? _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
)
: null,
onTap: onTap,
),
SizedBox(width: 12.w),
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: LocaleKeys.bloodPressure.tr(context: context),
value: (vitalSign.bloodPressureLower != null &&
vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 &&
vitalSign.bloodPressureHigher != 0)
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
: '--',
unit: '',
status: (vitalSign.bloodPressureLower != null &&
vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 &&
vitalSign.bloodPressureHigher != 0)
? _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
)
: null,
onTap: () => _openVitalSignDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.bloodPressure,
title: LocaleKeys.bloodPressure.tr(context: context),
icon: AppAssets.bloodPressure,
unit: 'mmHg',
low: 90,
high: 140,
),
vitalSign,
),
),
],
)),
),
],
),
),
];
}

@ -13,28 +13,30 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import '../../core/dependencies.dart';
class CollapsingListView extends StatelessWidget {
class CollapsingListView extends StatefulWidget {
final String title;
Widget? child;
VoidCallback? search;
VoidCallback? report;
VoidCallback? logout;
VoidCallback? aiOverview;
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
VoidCallback? sendEmail;
VoidCallback? doctorResponse;
VoidCallback? downloadReport;
VoidCallback? viewImage;
VoidCallback? location;
VoidCallback? downloadInvoice;
Widget? bottomChild;
Widget? trailing;
bool isClose;
bool isLeading;
VoidCallback? leadingCallback;
ScrollPhysics? physics;
final Widget? child;
final VoidCallback? search;
final VoidCallback? report;
final VoidCallback? logout;
final VoidCallback? aiOverview;
final VoidCallback? history;
final VoidCallback? instructions;
final VoidCallback? requests;
final VoidCallback? sendEmail;
final VoidCallback? doctorResponse;
final VoidCallback? downloadReport;
final VoidCallback? viewImage;
final VoidCallback? location;
final VoidCallback? downloadInvoice;
final Widget? bottomChild;
final Widget? trailing;
final bool isClose;
final bool isLeading;
final VoidCallback? leadingCallback;
final ScrollPhysics? physics;
final Widget? stickyWidget;
final double? stickyWidgetHeight;
CollapsingListView({
super.key,
@ -59,9 +61,28 @@ class CollapsingListView extends StatelessWidget {
this.trailing,
this.leadingCallback,
this.physics,
this.stickyWidget,
this.stickyWidgetHeight,
});
final ScrollController _controller = ScrollController();
@override
State<CollapsingListView> createState() => _CollapsingListViewState();
}
class _CollapsingListViewState extends State<CollapsingListView> {
late final ScrollController _controller;
@override
void initState() {
super.initState();
_controller = ScrollController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
@ -71,82 +92,66 @@ class CollapsingListView extends StatelessWidget {
body: Column(
children: [
CustomScrollView(
physics: physics,
physics: widget.physics,
controller: _controller,
slivers: [
SliverAppBar(
automaticallyImplyLeading: isLeading,
pinned: true,
toolbarHeight: 40.h,
leadingWidth: isLeading ? 40.w + 32.w : double.infinity,
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light),
surfaceTintColor: Colors.transparent,
backgroundColor: AppColors.bgScaffoldColor,
bottom:
// isLeading
// ?
ScrollAnimatedTitle(
title: title,
showBack: true,
controller: _controller,
search: search,
report: report,
logout: logout,
history: history,
instructions: instructions,
requests: requests,
sendEmail: sendEmail,
doctorResponse: doctorResponse,
downloadReport: downloadReport,
viewImage: viewImage,
location: location,
downloadInvoice: downloadInvoice,
bottomChild: bottomChild,
trailing: trailing,
aiOverview: aiOverview,
),
// : null,
leading: Visibility(
visible: isLeading,
child: Container(
height: 32.h,
width: 32.w,
alignment: appState.isArabic() ? Alignment.centerRight : Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h),
),
).onPress(() {
if (leadingCallback != null) {
leadingCallback!();
} else {
context.pop();
}
}),
)
// : ScrollAnimatedTitle(
// title: title,
// showBack: false,
// controller: _controller,
// search: search,
// report: report,
// logout: logout,
// history: history,
// instructions: instructions,
// requests: requests,
// sendEmail: sendEmail,
// bottomChild: bottomChild,
// trailing: trailing,
// ),
),
if (child != null)
automaticallyImplyLeading: widget.isLeading,
pinned: true,
toolbarHeight: 40.h,
leadingWidth: widget.isLeading ? 40.w + 32.w : double.infinity,
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light),
surfaceTintColor: Colors.transparent,
backgroundColor: AppColors.bgScaffoldColor,
bottom: ScrollAnimatedTitle(
title: widget.title,
showBack: true,
controller: _controller,
search: widget.search,
report: widget.report,
logout: widget.logout,
history: widget.history,
instructions: widget.instructions,
requests: widget.requests,
sendEmail: widget.sendEmail,
doctorResponse: widget.doctorResponse,
downloadReport: widget.downloadReport,
viewImage: widget.viewImage,
location: widget.location,
downloadInvoice: widget.downloadInvoice,
bottomChild: widget.bottomChild,
trailing: widget.trailing,
aiOverview: widget.aiOverview,
stickyWidget: widget.stickyWidget,
stickyWidgetHeight: widget.stickyWidgetHeight,
),
leading: Visibility(
visible: widget.isLeading,
child: Container(
height: 32.h,
width: 32.w,
alignment: appState.isArabic() ? Alignment.centerRight : Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: widget.isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h),
),
).onPress(() {
if (widget.leadingCallback != null) {
widget.leadingCallback!();
} else {
context.pop();
}
}),
),
),
if (widget.child != null)
SliverToBoxAdapter(
child: child!,
child: widget.child!,
),
],
).expanded,
if (bottomChild != null) bottomChild!
if (widget.bottomChild != null) widget.bottomChild!
],
),
);
@ -216,6 +221,8 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
VoidCallback? downloadInvoice;
Widget? bottomChild;
Widget? trailing;
Widget? stickyWidget;
double? stickyWidgetHeight;
ScrollAnimatedTitle({
super.key,
@ -237,10 +244,12 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
this.downloadInvoice,
this.bottomChild,
this.trailing,
this.stickyWidget,
this.stickyWidgetHeight,
});
@override
Size get preferredSize => const Size.fromHeight(56.0);
Size get preferredSize => Size.fromHeight(56.0 + (stickyWidget != null ? (stickyWidgetHeight ?? 58.h) : 0));
@override
State<ScrollAnimatedTitle> createState() => _ScrollAnimatedTitleState();
@ -254,6 +263,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
static const double _minHeight = 56;
double _fontSize = _maxFont;
double t = 1.0;
@override
void initState() {
@ -261,33 +271,53 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
widget.controller.addListener(_onScroll);
}
@override
void didUpdateWidget(covariant ScrollAnimatedTitle oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_onScroll);
widget.controller.addListener(_onScroll);
_onScroll();
}
}
@override
void dispose() {
widget.controller.removeListener(_onScroll);
super.dispose();
}
double t = 1.0;
void _onScroll() {
if (!widget.controller.hasClients) return;
final double offset = widget.controller.offset;
// control animation range
const double range = 120;
final double t = (1 - (offset / range)).clamp(0.0, 1.0);
this.t = t;
setState(() {
_fontSize = _minFont + (_maxFont - _minFont) * t;
});
final double newT = (1 - (offset / range)).clamp(0.0, 1.0);
if (newT != t) {
setState(() {
t = newT;
_fontSize = _minFont + (_maxFont - _minFont) * t;
});
}
}
@override
Widget build(BuildContext context) {
final isRtl = Directionality.of(context) == TextDirection.RTL;
return Container(
final double titleHeight = widget.stickyWidget != null
? (30.h + (44.h - 30.h) * t)
: 56.h;
final double topGap = widget.stickyWidget != null
? (2.h + (10.h - 2.h) * t)
: 0.0;
final titleWidget = Container(
// height: (widget.preferredSize.height - _fontSize / 2).h,
height: 56.h,
height: titleHeight,
alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft),
padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
child: Row(
@ -322,6 +352,24 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
],
),
);
if (widget.stickyWidget == null) {
return titleWidget;
}
return Container(
color: AppColors.bgScaffoldColor,
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
titleWidget,
SizedBox(height: topGap),
widget.stickyWidget!,
],
),
);
}
Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) {

Loading…
Cancel
Save