pull/206/head
haroon amjad 2 days ago
parent 1f290c5bfe
commit edeae70aa0

@ -1645,6 +1645,7 @@
"favouriteList": "قائمة المفضلة",
"later": "لاحقاً",
"cancelAppointmentConfirmMessage": "هل أنت متأكد من رغبتك في إلغاء هذا الموعد؟",
"acknowledged": "معترف به"
"acknowledged": "معترف به",
"searchLabResults": "بحث نتائج المختبر"
}

@ -1637,5 +1637,6 @@
"favouriteList": "Favourite List",
"later": "Later",
"cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?",
"acknowledged": "Acknowledged"
"acknowledged": "Acknowledged",
"searchLabResults": "Search lab results"
}

@ -516,7 +516,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
List<PatientAppointmentHistoryResponseModel> appointmentsList =
list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientAppointmentHistoryResponseModel>();
appointmentsList.removeWhere((element) => element.isActiveDoctorProfile == false);
// appointmentsList.removeWhere((element) => element.isActiveDoctorProfile == false);
apiResponse = GenericApiModel<List<PatientAppointmentHistoryResponseModel>>(
messageStatus: messageStatus,

@ -7,13 +7,15 @@ class NotificationsViewModel extends ChangeNotifier {
bool isNotificationsLoading = false;
bool hasMoreNotifications = true;
int unreadNotificationsCount = 0;
NotificationsRepo notificationsRepo;
ErrorHandlerService errorHandlerService;
List<NotificationResponseModel> notificationsList = [];
int currentPage = 0;
int pagingSize = 14;
int pagingSize = 15;
int notificationStatusID = 2; // Default to status 2 (e.g., unread)
NotificationsViewModel({
@ -46,6 +48,7 @@ class NotificationsViewModel extends ChangeNotifier {
Function(String)? onError,
}) async {
isNotificationsLoading = true;
unreadNotificationsCount = 0;
notifyListeners();
final result = await notificationsRepo.getAllNotifications(
@ -78,6 +81,11 @@ class NotificationsViewModel extends ChangeNotifier {
}
notificationsList.addAll(newNotifications);
for (var notification in notificationsList) {
if (notification.isRead == false) {
unreadNotificationsCount++;
}
}
currentPage++;
notifyListeners();

@ -1638,5 +1638,6 @@ abstract class LocaleKeys {
static const later = 'later';
static const cancelAppointmentConfirmMessage = 'cancelAppointmentConfirmMessage';
static const acknowledged = 'acknowledged';
static const searchLabResults = 'searchLabResults';
}

@ -162,6 +162,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
onConfirmTap: () async {
Navigator.of(context).pop();
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr(context: context));
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
await myAppointmentsViewModel.cancelAppointment(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) async {

@ -55,36 +55,40 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.appointmentsList.tr(context: context),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)),
],
onTabChange: (index) {
setState(() {
expandedIndex = null;
});
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
},
).paddingSymmetrical(24.h, 0.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM);
}),
],
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return CollapsingListView(
title: LocaleKeys.appointmentsList.tr(context: context),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 16.h),
CustomTabBar(
initialIndex: myAppointmentsVM.selectedTabIndex,
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)),
],
onTabChange: (index) {
setState(() {
expandedIndex = null;
});
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
},
).paddingSymmetrical(24.h, 0.h),
// Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// return
getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM),
// }),
],
),
),
),
),
);
}),
);
}

@ -323,6 +323,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
);
});
},
isDisabled: doctor?.isActiveDoctorProfile == false,
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,

@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' show RegionBottomSheetBody;
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
@ -150,7 +151,25 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.isAuthenticated) ...[],
LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyDoctorsPage(),
),
);
}),
],
),
SizedBox(height: 16.h),
SizedBox(
height: 110.h,

@ -1,6 +1,7 @@
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';
@ -58,7 +59,17 @@ class WelcomeWidget extends StatelessWidget {
children: [
Flexible(child: name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1, isEnglishOnly: true)),
// Icon(Icons.keyboard_arrow_down, size: 20, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w)
Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
iconColor: AppColors.blackColor,
width: 22.w,
height: 22.h,
fit: BoxFit.contain,
)
),
// Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w)
],
),
],

File diff suppressed because one or more lines are too long

@ -78,8 +78,9 @@ class _SearchLabResultsContentState extends State<SearchLabResultsContent> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextInputWidget(
labelText: "Search lab results",
hintText: "Type test name",
fontFamily: "Poppins",
labelText: LocaleKeys.searchLabResults.tr(context: context),
hintText: "",
controller: searchEditingController,
isEnable: true,
prefix: null,

@ -17,145 +17,114 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
class NotificationsListPage extends StatelessWidget {
class NotificationsListPage extends StatefulWidget {
const NotificationsListPage({super.key});
@override
State<NotificationsListPage> createState() => _NotificationsListPageState();
}
class _NotificationsListPageState extends State<NotificationsListPage> {
final GlobalKey _bottomKey = GlobalKey();
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _bottomKey.currentContext;
if (ctx != null) {
Scrollable.ensureVisible(
ctx,
duration: const Duration(milliseconds: 400),
curve: Curves.easeOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: LocaleKeys.notification.tr(context: context),
child: SingleChildScrollView(
child: Consumer<NotificationsViewModel>(builder: (context, notificationsVM, child) {
return Container(
margin: EdgeInsets.symmetric(vertical: 24.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading ? 4 : notificationsVM.notificationsList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return notificationsVM.isNotificationsLoading
? LabResultItemView(
child: Consumer<NotificationsViewModel>(builder: (context, notificationsVM, child) {
return Column(
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 24.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading
? 4
: notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
if (notificationsVM.isNotificationsLoading) {
return LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () {
notificationsVM.markAsRead(notificationID: notificationsVM.notificationsList[index].id ?? 0);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailsPage(
notification: notificationsVM.notificationsList[index],
),
),
);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
// Message row with red dot for unread
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: notificationsVM.notificationsList[index].message!.toText16(
isBold: (notificationsVM.notificationsList[index].isRead == false),
weight: (notificationsVM.notificationsList[index].isRead == false)
? FontWeight.w600
: FontWeight.w400,
),
);
}
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () {
notificationsVM.markAsRead(notificationID: notificationsVM.notificationsList[index].id ?? 0);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailsPage(
notification: notificationsVM.notificationsList[index],
),
SizedBox(width: 8.w),
// Red dot for unread notifications ONLY
if (notificationsVM.notificationsList[index].isRead == false)
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
],
),
SizedBox(height: 12.h),
// First row: Time and Date chips with arrow
Row(
),
);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Time chip with clock icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.access_time, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatTime(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
SizedBox(height: 16.h),
// Message row with red dot for unread
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: notificationsVM.notificationsList[index].message!.toText16(
isBold: (notificationsVM.notificationsList[index].isRead == false),
weight: (notificationsVM.notificationsList[index].isRead == false)
? FontWeight.w600
: FontWeight.w400,
),
],
),
),
SizedBox(width: 8.w),
// Date chip with calendar icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatDate(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
SizedBox(width: 8.w),
// Red dot for unread notifications ONLY
if (notificationsVM.notificationsList[index].isRead == false)
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
],
),
),
Spacer(),
// Arrow icon
Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 16.w,
height: 16.h,
iconColor: AppColors.greyTextColor,
],
),
],
),
// Second row: Contains Image chip (if MessageType is "image")
if (notificationsVM.notificationsList[index].messageType != null &&
notificationsVM.notificationsList[index].messageType!.toLowerCase() == "image")
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
SizedBox(height: 12.h),
// First row: Time and Date chips with arrow
Row(
children: [
// Time chip with clock icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
@ -165,31 +134,100 @@ class NotificationsListPage extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor),
Icon(Icons.access_time, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
'Contains Image'.toText10(
_formatTime(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
SizedBox(width: 8.w),
// Date chip with calendar icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatDate(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
Spacer(),
// Arrow icon
Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 16.w,
height: 16.h,
iconColor: AppColors.greyTextColor,
),
],
),
),
SizedBox(height: 16.h),
1.divider,
],
// Second row: Contains Image chip (if MessageType is "image")
if (notificationsVM.notificationsList[index].messageType != null &&
notificationsVM.notificationsList[index].messageType!.toLowerCase() == "image")
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
'Contains Image'.toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
],
),
),
SizedBox(height: 16.h),
1.divider,
],
),
),
),
),
),
),
),
);
}).paddingSymmetrical(16.w, 0.h),
).paddingSymmetrical(24.w, 0.h);
);
}).paddingSymmetrical(16.w, 0.h),
).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
SizedBox(
key: _bottomKey,
child: "Show more notifications".toText16(
color: AppColors.primaryRedColor,
isBold: true,
isUnderLine: true
),
).onPress(() async {
await notificationsVM.loadMoreNotifications();
_scrollToBottom();
}),
SizedBox(height: 24.h),
],
);
}),
),
);
}

@ -57,9 +57,9 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: Column(
children: [
languageItem("English", "en"),
1.divider,
languageItem("العربية", "ar"),
1.divider,
languageItem("English", "en"),
],
),
),

@ -48,6 +48,14 @@ class CustomTabBarState extends State<CustomTabBar> {
super.initState();
}
@override
void didUpdateWidget(covariant CustomTabBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialIndex != widget.initialIndex) {
selectedIndex = widget.initialIndex;
}
}
@override
void dispose() {
super.dispose();

@ -289,15 +289,7 @@ class TextInputWidget extends StatelessWidget {
}
Widget _buildLabelText(Color? labelColor) {
return Text(
labelText,
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w500,
color: labelColor ?? AppColors.inputLabelTextColor,
letterSpacing: -0,
),
);
return labelText.toText12(fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor);
}
Widget _buildTextField(BuildContext context) {

Loading…
Cancel
Save