diff --git a/lib/features/book_appointments/models/timeslots.dart b/lib/features/book_appointments/models/timeslots.dart index a5da681..8fc7b15 100644 --- a/lib/features/book_appointments/models/timeslots.dart +++ b/lib/features/book_appointments/models/timeslots.dart @@ -3,6 +3,7 @@ class TimeSlot { DateTime? start; DateTime? end; String? vidaDate; + bool isSelected = false; TimeSlot({required this.isoTime, required this.start, required this.end, this.vidaDate}); } diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index adcfce2..b7b8eb5 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -119,14 +119,13 @@ class DoctorProfilePage extends StatelessWidget { isBookingForLiveCare: false, onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( title: "Pick a Date", context, child: AppointmentCalendar(), - callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, + callBackFunc: () {}, ); // //TODO: Calendar design to be changed as per new design & handle Dubai & KSA both cases diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart new file mode 100644 index 0000000..f942ebf --- /dev/null +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -0,0 +1,203 @@ +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'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +class ReviewAppointmentPage extends StatefulWidget { + const ReviewAppointmentPage({super.key}); + + @override + State createState() => _ReviewAppointmentPageState(); +} + +class _ReviewAppointmentPageState extends State { + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + late AuthenticationViewModel authVM; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + authVM = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.reviewAppointment.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.docInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + bookAppointmentsViewModel.selectedDoctor.doctorImageURL!, + width: 50.h, + height: 50.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.49, + child: + "${bookAppointmentsViewModel.selectedDoctor.doctorTitle} ${bookAppointmentsViewModel.selectedDoctor.name}".toString().toText16(isBold: true, maxlines: 1), + ), + Image.network( + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!, + width: 20.h, + height: 15.h, + fit: BoxFit.fill, + ), + ], + ), + SizedBox(height: 2.h), + (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1), + ], + ), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 8.h, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}".needTranslation, + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.branch.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation, + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.selectedAppointmentDate}".needTranslation, + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.time.tr(context: context)}: ${bookAppointmentsViewModel.selectedAppointmentTime}".needTranslation, + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 24.h), + LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + width: 52.h, + height: 52.h, + ), + SizedBox(width: 8.h), + Column( + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + SizedBox(height: 8.h), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + ], + ), + ], + ), + ), + ), + SizedBox(height: 24.h), + "Hospital Information".needTranslation.toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true), + ], + ), + ), + ), + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () async { + initiateBookAppointment(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); + } + + void initiateBookAppointment() { + + } +} diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 3bb3b9f..bc56441 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -3,16 +3,25 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.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'; 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/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/review_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; @@ -29,8 +38,10 @@ class _AppointmentCalendarState extends State { late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; + late AuthenticationViewModel authVM; var selectedDate = ""; + var selectedDateDisplay = ""; var selectedNextDate = ""; int selectedButtonIndex = 0; @@ -41,7 +52,7 @@ class _AppointmentCalendarState extends State { late Map _events; - static String? selectedTime; + String selectedTime = ""; bool isWaitingAppointmentAvailable = false; final _selectedDay = DateTime.now(); @@ -61,80 +72,188 @@ class _AppointmentCalendarState extends State { @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); + authVM = Provider.of(context, listen: false); appState = getIt.get(); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 380.h, - child: SfCalendar( - controller: _calendarController, - minDate: DateTime.now(), - showNavigationArrow: true, - headerHeight: 60.h, - headerStyle: CalendarHeaderStyle( - backgroundColor: AppColors.scaffoldBgColor, - textAlign: TextAlign.start, - textStyle: TextStyle(fontSize: 18.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"), - ), - viewHeaderStyle: ViewHeaderStyle( - backgroundColor: AppColors.scaffoldBgColor, - dayTextStyle: TextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor), - ), - view: CalendarView.month, - todayHighlightColor: Colors.transparent, - todayTextStyle: TextStyle(color: AppColors.textColor), - selectionDecoration: ShapeDecoration( - color: AppColors.transparent, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(12 ?? 0), - smoothness: 1, - side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + return Padding( + padding: EdgeInsets.symmetric(horizontal: 0.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox(height: 24.h), + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // "Pick a Date".toText20(weight: FontWeight.w600).expanded, + // Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { + // Navigator.of(context).pop(); + // }), + // ], + // ), + SizedBox( + height: 350.h, + child: SfCalendar( + controller: _calendarController, + minDate: DateTime.now(), + showNavigationArrow: true, + headerHeight: 60.h, + headerStyle: CalendarHeaderStyle( + backgroundColor: AppColors.scaffoldBgColor, + textAlign: TextAlign.start, + textStyle: TextStyle(fontSize: 18.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"), + ), + viewHeaderStyle: ViewHeaderStyle( + backgroundColor: AppColors.scaffoldBgColor, + dayTextStyle: TextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor), + ), + view: CalendarView.month, + todayHighlightColor: Colors.transparent, + todayTextStyle: TextStyle(color: AppColors.textColor), + selectionDecoration: ShapeDecoration( + color: AppColors.transparent, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(12 ?? 0), + smoothness: 1, + side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + ), + ), + cellBorderColor: AppColors.transparent, + dataSource: MeetingDataSource(_getDataSource()), + monthCellBuilder: (context, details) => Padding( + padding: EdgeInsets.all(12.h), + child: details.date.day.toString().toText14( + isCenter: true, + color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, + ), + ), + monthViewSettings: MonthViewSettings( + dayFormat: "EEE", + appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, + showTrailingAndLeadingDates: false, + appointmentDisplayCount: 1, + monthCellStyle: MonthCellStyle( + textStyle: TextStyle(fontSize: 19.fSize), + ), ), + onTap: (CalendarTapDetails details) { + _calendarController.selectedDate = details.date; + _onDaySelected(details.date!); + }, ), - cellBorderColor: AppColors.transparent, - dataSource: MeetingDataSource(_getDataSource()), - monthCellBuilder: (context, details) => Padding( - padding: EdgeInsets.all(12.h), - child: details.date.day.toString().toText14( - isCenter: true, - color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, + ), + Transform.translate( + offset: const Offset(0.0, -20.0), + child: selectedDateDisplay.toText16(weight: FontWeight.w500), + ), + //TODO: Add Next Day Span here + SizedBox( + height: 100.h, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, + spacing: 8.h, + runSpacing: 8.h, + children: List.generate( + dayEvents.length, // Generate a large number of items to ensure scrolling + (index) => TimeSlotChip( + label: dayEvents[index].isoTime!, + isSelected: index == selectedButtonIndex, + onTap: () { + setState(() { + selectedButtonIndex = index; + selectedTime = dayEvents[index].isoTime!; + }); + }, ), - ), - monthViewSettings: MonthViewSettings( - dayFormat: "EEE", - appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, - showTrailingAndLeadingDates: false, - appointmentDisplayCount: 1, - monthCellStyle: MonthCellStyle( - textStyle: TextStyle(fontSize: 19.fSize), + ), ), ), - onTap: (CalendarTapDetails details) { - _calendarController.selectedDate = details.date; - _onDaySelected(details.date!); - }, ), - ), - //TODO: Add Next Day Span here - Container( - height: 40, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: dayEvents.length, - itemBuilder: (context, index) { - return Container( - margin: EdgeInsets.only(right: (index == dayEvents.length - 1) ? 16 : 5.0, left: index == 0 ? 16 : 5), - child: AppCustomChipWidget( - labelText: dayEvents[index].isoTime, - backgroundColor: AppColors.whiteColor, - ), - // index == selectedButtonIndex ? getSelectedButton(index) : getNormalButton(index), - ); + SizedBox(height: 16.h), + CustomButton( + text: "Select".needTranslation, + onPressed: () async { + if (appState.isAuthenticated) { + bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); + Navigator.of(context).pop(); + Navigator.of(context).push( + FadePage( + page: ReviewAppointmentPage(), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + await authVM.onLoginPressed(); + // Navigator.of(context).push( + // FadePage(page: MyAppointmentsPage()), + // ); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + SizedBox(height: 16.h), + ], + ).center, + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, ), - ), - ], + ], + ), ); } @@ -173,13 +292,13 @@ class _AppointmentCalendarState extends State { Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; timeList.add(timeSlot); } - selectedTime = dayEvents[selectedButtonIndex].isoTime; + selectedTime = dayEvents[selectedButtonIndex].isoTime!; } void _onDaySelected(DateTime day) { final DateFormat formatter = DateFormat('yyyy-MM-dd'); setState(() { - selectedDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day, "en"); + selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); _calendarController.selectedDate = day; openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots); @@ -190,6 +309,36 @@ class _AppointmentCalendarState extends State { } } +class TimeSlotChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const TimeSlotChip({super.key, required this.label, this.isSelected = false, this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 18.h, vertical: 8.h), + decoration: ShapeDecoration( + color: AppColors.whiteColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.h), + smoothness: 1, + side: BorderSide(color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), + ), + ), + child: label.toText12( + color: isSelected ? AppColors.primaryRedColor : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} + class MeetingDataSource extends CalendarDataSource { MeetingDataSource(List source) { appointments = source;