diff --git a/lib/common/const/const.dart b/lib/common/const/const.dart index 6e0d7af..7fa8eef 100644 --- a/lib/common/const/const.dart +++ b/lib/common/const/const.dart @@ -19,7 +19,6 @@ extension Code on Languages { class AppColors { static Color sentMessageColor = const Color(0xff7986ca); static Color primeColor = const Color(0xff7986ca); - static Color tailAuthColor = const Color(0xffFEE64B); static Color textButtonColor = const Color(0xff484848); static Color textColor = Colors.black; static Color borderColor = const Color(0xffD9D9D9); @@ -28,6 +27,7 @@ class AppColors { static Color textMessageColor = const Color(0xff4E5D78); static Color timeMessageColor = const Color(0xff9FA6B5); static Color secondaryColor = const Color(0xff44bee8); + static Color tailAuthColor = secondaryColor; static Color emailColor = const Color(0xff76BAD0); static Color callColor = const Color(0xff76D095); static Color messageColor = const Color(0xff4E5D78); diff --git a/lib/common/widgets/header_screen.dart b/lib/common/widgets/header_screen.dart index 3acee5a..8211455 100644 --- a/lib/common/widgets/header_screen.dart +++ b/lib/common/widgets/header_screen.dart @@ -23,24 +23,28 @@ class HeaderScreen extends StatelessWidget { return Row( children: [ SizedBox( - width: 24, - child: Obx(() => (!homeController.isArabic.value) - ? SvgPicture.asset( - "assets/icons/arrow-left.svg", - width: Responsive.isTablet() ? 25 : null, - colorFilter: (iconColor != null) - ? ColorFilter.mode(iconColor!, BlendMode.srcIn) - : null, - ) - : SvgPicture.asset( - "assets/icons/arrow right.svg", - width: Responsive.isTablet() ? 20 : null, - colorFilter: (iconColor != null) - ? ColorFilter.mode(iconColor!, BlendMode.srcIn) - : null, - )).onTap(() { + width: 32, + child: Obx( + () => (!homeController.isArabic.value) + ? SvgPicture.asset( + "assets/icons/arrow-left.svg", + width: Responsive.isTablet() ? 25 : null, + colorFilter: (iconColor != null) + ? ColorFilter.mode(iconColor!, BlendMode.srcIn) + : null, + ) + : SvgPicture.asset( + "assets/icons/arrow right.svg", + width: Responsive.isTablet() ? 20 : null, + colorFilter: (iconColor != null) + ? ColorFilter.mode(iconColor!, BlendMode.srcIn) + : null, + ), + ), + ).onTap( + () { RoutingManager.back(); - }), + }, ), BoldTextWidget( title, diff --git a/lib/core/localization/localization.dart b/lib/core/localization/localization.dart index 406fb9d..d54190b 100644 --- a/lib/core/localization/localization.dart +++ b/lib/core/localization/localization.dart @@ -4,7 +4,14 @@ class PagesTranslations implements Translations { @override Map> get keys => { 'en': { - 'you_have_an_appointment_on': 'your appointment on', + 'appointment': 'Appointment', + 'you_have_an_appointment_on': 'You have An Appointment on', + 'appointment_scheduled_successfully.': + 'Appointment scheduled successfully.', + 'am': 'AM', + 'pm': 'PM', + 'available_times': 'Available Times', + 'you_have_an_appointment': 'Details', 'schedule_an_appointment': 'Appointment', 'There are no notifications to display at this time.': 'There are no notifications to display at this time.', @@ -184,7 +191,13 @@ class PagesTranslations implements Translations { 'Enter your email to reset your password please \n We will send verification code to your Email.', }, 'ar': { + 'appointment': 'موعد', 'you_have_an_appointment_on': 'لديك موعد بتاريخ', + 'appointment_scheduled_successfully.': 'تم حجز الموعد بنجاح', + 'am': 'صباحاً', + 'pm': 'مساءً', + 'available_times': 'الأوقات المتاحة', + 'you_have_an_appointment': 'التفاصيل', 'schedule_an_appointment': 'حجز موعد', 'There are no notifications to display at this time.': 'لا توجد إشعارات لعرضها في الوقت الحالي.', diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 23d8907..b635c1b 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -59,6 +59,29 @@ class Utils { } } + static DateTime generateRandomDateTime() { + final random = Random(); + DateTime currentDate = DateTime.now(); + + final year = currentDate.year; + + final month = currentDate.month; + + final maxDaysInMonth = 30 - currentDate.day; + + final day = currentDate.day + 1 + random.nextInt(maxDaysInMonth) + 1; + + final hour = 12 + random.nextInt(5) + 1; + + final minute = random.nextInt(60); + + final second = random.nextInt(60); + + final millisecond = random.nextInt(1000); + + return DateTime(year, month, day, hour, minute, second, millisecond); + } + static Future?> pickImage(BuildContext context) async { return await AssetPicker.pickAssets( context, diff --git a/lib/features/card/business_logic_layer/card_controller.dart b/lib/features/card/business_logic_layer/card_controller.dart index c61a4bd..72e8922 100644 --- a/lib/features/card/business_logic_layer/card_controller.dart +++ b/lib/features/card/business_logic_layer/card_controller.dart @@ -238,4 +238,12 @@ class CardController extends GetxController { onError?.call(e); }, onSuccess: onSuccess); } + + /// ------------ card appointments ---------/// + RxFuture availableAppointmentsState = RxFuture(null); + void getAvailableAppointments() async { + return availableAppointmentsState.observe((value) async { + await Future.delayed(const Duration(seconds: 3)); + }); + } } diff --git a/lib/features/card/presentation_layer/screens/card_details.dart b/lib/features/card/presentation_layer/screens/card_details.dart index 9319dc1..e1db407 100644 --- a/lib/features/card/presentation_layer/screens/card_details.dart +++ b/lib/features/card/presentation_layer/screens/card_details.dart @@ -6,6 +6,7 @@ import 'package:taafee_mobile/common/extensions/widget_extension.dart'; import 'package:taafee_mobile/common/widgets/button.dart'; import 'package:taafee_mobile/common/widgets/text.dart'; import 'package:taafee_mobile/core/routing/routing_manager.dart'; +import 'package:taafee_mobile/core/utils/utils.dart'; import 'package:taafee_mobile/features/card/business_logic_layer/card_controller.dart'; import 'package:taafee_mobile/features/card/data_layer/model/appointment.dart'; import 'package:taafee_mobile/features/card/presentation_layer/widgets/card_details.dart'; @@ -16,6 +17,8 @@ import '../../../../common/widgets/toast.dart'; import '../../../../core/url launcher/url_launcher_service.dart'; import '../../../home/business_logic_layer/home_controller.dart'; import '../../data_layer/model/card_model.dart'; +import '../widgets/appointment_details.dart'; +import '../widgets/appointment_widget.dart'; import '../widgets/card_email.dart'; import '../widgets/card_header.dart'; import '../widgets/card_image.dart'; @@ -30,6 +33,93 @@ class CardDetailsScreen extends StatelessWidget { final ChatController chatController = Get.find(); @override Widget build(BuildContext context) { + void appointmentSchedulingDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + contentPadding: EdgeInsets.zero, + actionsPadding: EdgeInsets.zero, + title: RegularTextWidget( + "available_times".tr, + fontSize: 14, + ), + content: Obx(() { + if (cardController.availableAppointmentsState.loading || + homeController.appointmentSchedulingState.loading) { + return SizedBox( + width: 320, + height: 320, + child: const CircularProgressIndicator().center()); + } + return SizedBox( + height: 320, + width: 320, + child: Column( + children: [ + const SizedBox( + height: 20, + ), + SizedBox( + height: 280, + child: ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: 12, + itemBuilder: (context, index) { + DateTime dateTime = Utils.generateRandomDateTime(); + return AppointmentWidget(dateTime: dateTime) + .onTap(() { + homeController.scheduleAnAppointment( + Appointment( + cardId: cardModel.id, + dateTime: dateTime, + user: homeController.user.value!), + onSuccess: () { + Toast.showToast( + 'appointment_scheduled_successfully.'.tr); + RoutingManager.back(); + }); + }); + }, + separatorBuilder: (context, index) { + return const Divider(); + }, + ).paddingOnly(left: 8, right: 8, top: 8, bottom: 8), + ), + const SizedBox( + height: 20, + ), + ], + ), + ); + }), + ); + }); + } + + void appointmentDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + contentPadding: EdgeInsets.zero, + actionsPadding: EdgeInsets.zero, + title: RegularTextWidget( + "appointment".tr, + textAlign: TextAlign.center, + fontSize: 14, + ), + content: AppointmentDetails( + dateTime: + homeController.currentCardAppointment.value!.dateTime), + ); + }); + } + cardController.updateCardNetworkImageUrls(cardModel.cardImages); return Scaffold( backgroundColor: AppColors.backGroundColor, @@ -262,28 +352,43 @@ class CardDetailsScreen extends StatelessWidget { }), ), if (Responsive.isTablet()) - Visibility( - visible: homeController.user.value!.id != cardModel.user.id, - child: ButtonWidget( - onTap: () { - homeController.scheduleAnAppointment( - Appointment( - cardId: cardModel.id, - dateTime: DateTime.now(), - user: homeController.user.value!), - ); - }, - haveIcon: true, - title: 'schedule_an_appointment'.tr, - color: AppColors.secondaryColor, - child: const Icon( - Icons.schedule, - color: Colors.white, - ), - ) - .paddingSymmetric(horizontal: 10) - .expanded(Responsive.isTablet() ? 1 : 5), - ), + Obx(() { + return Visibility( + visible: homeController.user.value!.id != cardModel.user.id, + child: (homeController.currentCardAppointment.value != null) + ? ButtonWidget( + haveIcon: true, + onTap: () { + appointmentDialog(context); + }, + fontSize: 12, + color: AppColors.secondaryColor, + title: 'you_have_an_appointment'.tr, + child: const Icon( + Icons.schedule, + color: Colors.white, + ), + ) + .paddingSymmetric(horizontal: 4) + .expanded(Responsive.isTablet() ? 1 : 5) + : ButtonWidget( + onTap: () { + cardController.getAvailableAppointments(); + appointmentSchedulingDialog(context); + }, + haveIcon: true, + fontSize: 12, + title: 'schedule_an_appointment'.tr, + color: AppColors.secondaryColor, + child: const Icon( + Icons.schedule, + color: Colors.white, + ), + ) + .paddingSymmetric(horizontal: 4) + .expanded(Responsive.isTablet() ? 1 : 5), + ); + }), ], ).paddingSymmetric( vertical: Responsive.isTablet() ? 20 : 4, @@ -299,22 +404,24 @@ class CardDetailsScreen extends StatelessWidget { child: (homeController.currentCardAppointment.value != null) ? ButtonWidget( - onTap: () {}, + haveIcon: true, + onTap: () { + appointmentDialog(context); + }, fontSize: 12, color: AppColors.secondaryColor, - title: - '${'you_have_an_appointment_on'.tr} ${homeController.currentCardAppointment.value!.dateTime.toString().substring(0, 11)}', + title: 'you_have_an_appointment'.tr, + child: const Icon( + Icons.schedule, + color: Colors.white, + ), ) .paddingSymmetric(horizontal: 4) .expanded(Responsive.isTablet() ? 1 : 5) : ButtonWidget( onTap: () { - homeController.scheduleAnAppointment( - Appointment( - cardId: cardModel.id, - dateTime: DateTime.now(), - user: homeController.user.value!), - ); + cardController.getAvailableAppointments(); + appointmentSchedulingDialog(context); }, haveIcon: true, fontSize: 12, diff --git a/lib/features/card/presentation_layer/widgets/appointment_details.dart b/lib/features/card/presentation_layer/widgets/appointment_details.dart new file mode 100644 index 0000000..ac5d354 --- /dev/null +++ b/lib/features/card/presentation_layer/widgets/appointment_details.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; + +import '../../../../common/const/const.dart'; +import '../../../../common/widgets/text.dart'; +import '../../../home/business_logic_layer/home_controller.dart'; + +class AppointmentDetails extends StatelessWidget { + AppointmentDetails({ + super.key, + required this.dateTime, + }); + + final DateTime dateTime; + final HomeController homeController = Get.find(); + @override + Widget build(BuildContext context) { + return SizedBox( + width: 320, + height: 240, + child: Column( + children: [ + const SizedBox( + height: 16, + ), + RegularTextWidget( + 'you_have_an_appointment_on'.tr, + fontSize: 15, + ), + const SizedBox( + height: 20, + ), + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: AppColors.secondaryColor.withOpacity(0.7), + ), + child: Column( + children: [ + RegularTextWidget( + DateFormat.EEEE().format(dateTime).tr, + fontSize: 15, + color: Colors.white, + ), + Row( + children: [ + const Icon( + Icons.calendar_month, + color: Colors.white, + ), + RegularTextWidget( + ' ${dateTime.day} / ${dateTime.month}', + fontSize: 15, + color: Colors.white, + ), + ], + ), + Row( + children: [ + const Icon( + Icons.schedule, + color: Colors.white, + ), + if (!homeController.isArabic.value) + RegularTextWidget( + ' ${dateTime.hour % 12} : ${dateTime.minute >= 10 ? dateTime.minute : '0${dateTime.minute}'} ${(dateTime.hour <= 12) ? 'am'.tr : 'pm'.tr}', + color: Colors.white, + fontSize: 15, + ), + if (homeController.isArabic.value) + RegularTextWidget( + ' ${dateTime.minute >= 10 ? dateTime.minute : '0${dateTime.minute}'} : ${dateTime.hour % 12} ${(dateTime.hour <= 12) ? 'am'.tr : 'pm'.tr}', + color: Colors.white, + fontSize: 15, + ), + ], + ), + ], + ), + ).paddingSymmetric(horizontal: 16) + ], + ), + ); + } +} diff --git a/lib/features/card/presentation_layer/widgets/appointment_widget.dart b/lib/features/card/presentation_layer/widgets/appointment_widget.dart new file mode 100644 index 0000000..3faf2be --- /dev/null +++ b/lib/features/card/presentation_layer/widgets/appointment_widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:taafee_mobile/features/home/business_logic_layer/home_controller.dart'; + +import '../../../../common/const/const.dart'; +import '../../../../common/widgets/text.dart'; + +class AppointmentWidget extends StatelessWidget { + AppointmentWidget({ + super.key, + required this.dateTime, + }); + final DateTime dateTime; + final HomeController homeController = Get.find(); + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColors.tailAuthColor.withOpacity(0.5), + ), + child: SizedBox( + width: 100, + height: 40, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.date_range, + color: Colors.black87, + ), + RegularTextWidget( + DateFormat.EEEE().format(dateTime).tr, + color: Colors.black87, + ), + RegularTextWidget( + '${dateTime.day} / ${dateTime.month}', + color: Colors.black87, + ), + if (!homeController.isArabic.value) + RegularTextWidget( + '${dateTime.hour % 12} : ${dateTime.minute >= 10 ? dateTime.minute : '0${dateTime.minute}'} ${(dateTime.hour <= 12) ? 'am'.tr : 'pm'.tr}', + color: Colors.black87, + ), + if (homeController.isArabic.value) + RegularTextWidget( + '${dateTime.minute >= 10 ? dateTime.minute : '0${dateTime.minute}'} : ${dateTime.hour % 12} ${(dateTime.hour <= 12) ? 'am'.tr : 'pm'.tr}', + color: Colors.black87, + ), + ]), + ), + ); + } +} diff --git a/lib/features/home/business_logic_layer/home_controller.dart b/lib/features/home/business_logic_layer/home_controller.dart index 222a7c2..0b340d3 100644 --- a/lib/features/home/business_logic_layer/home_controller.dart +++ b/lib/features/home/business_logic_layer/home_controller.dart @@ -182,21 +182,31 @@ class HomeController extends GetxController { user.refresh(); } - void scheduleAnAppointment(Appointment appointment) async { - user.update((val) { - if (val!.appointments != null) { - val.appointments!.add(appointment); - } else { - val.appointments = []; - val.appointments!.add(appointment); - } - }); + RxFuture appointmentSchedulingState = RxFuture(null); + void scheduleAnAppointment(Appointment appointment, + {void Function()? onSuccess}) async { + appointmentSchedulingState.observe( + (value) async { + await Future.delayed(const Duration(seconds: 3)); + }, + onSuccess: (response) async { + user.update((val) { + if (val!.appointments != null) { + val.appointments!.add(appointment); + } else { + val.appointments = []; + val.appointments!.add(appointment); + } + }); - await storage.setAppointments(user.value!.appointments!); + await storage.setAppointments(user.value!.appointments!); - currentCardAppointment = appointment.obs; - currentCardAppointment.refresh(); - user.refresh(); + currentCardAppointment = appointment.obs; + currentCardAppointment.refresh(); + user.refresh(); + onSuccess?.call(); + }, + ); } Appointment? getCardAppointment(int cardId) {