appointments ux optimization

This commit is contained in:
MhdZiadHirati 2023-10-22 15:42:12 +03:00
parent 11dbb32a1a
commit b605346c3c
9 changed files with 371 additions and 63 deletions

View File

@ -19,7 +19,6 @@ extension Code on Languages {
class AppColors { class AppColors {
static Color sentMessageColor = const Color(0xff7986ca); static Color sentMessageColor = const Color(0xff7986ca);
static Color primeColor = const Color(0xff7986ca); static Color primeColor = const Color(0xff7986ca);
static Color tailAuthColor = const Color(0xffFEE64B);
static Color textButtonColor = const Color(0xff484848); static Color textButtonColor = const Color(0xff484848);
static Color textColor = Colors.black; static Color textColor = Colors.black;
static Color borderColor = const Color(0xffD9D9D9); static Color borderColor = const Color(0xffD9D9D9);
@ -28,6 +27,7 @@ class AppColors {
static Color textMessageColor = const Color(0xff4E5D78); static Color textMessageColor = const Color(0xff4E5D78);
static Color timeMessageColor = const Color(0xff9FA6B5); static Color timeMessageColor = const Color(0xff9FA6B5);
static Color secondaryColor = const Color(0xff44bee8); static Color secondaryColor = const Color(0xff44bee8);
static Color tailAuthColor = secondaryColor;
static Color emailColor = const Color(0xff76BAD0); static Color emailColor = const Color(0xff76BAD0);
static Color callColor = const Color(0xff76D095); static Color callColor = const Color(0xff76D095);
static Color messageColor = const Color(0xff4E5D78); static Color messageColor = const Color(0xff4E5D78);

View File

@ -23,8 +23,9 @@ class HeaderScreen extends StatelessWidget {
return Row( return Row(
children: [ children: [
SizedBox( SizedBox(
width: 24, width: 32,
child: Obx(() => (!homeController.isArabic.value) child: Obx(
() => (!homeController.isArabic.value)
? SvgPicture.asset( ? SvgPicture.asset(
"assets/icons/arrow-left.svg", "assets/icons/arrow-left.svg",
width: Responsive.isTablet() ? 25 : null, width: Responsive.isTablet() ? 25 : null,
@ -38,9 +39,12 @@ class HeaderScreen extends StatelessWidget {
colorFilter: (iconColor != null) colorFilter: (iconColor != null)
? ColorFilter.mode(iconColor!, BlendMode.srcIn) ? ColorFilter.mode(iconColor!, BlendMode.srcIn)
: null, : null,
)).onTap(() { ),
),
).onTap(
() {
RoutingManager.back(); RoutingManager.back();
}), },
), ),
BoldTextWidget( BoldTextWidget(
title, title,

View File

@ -4,7 +4,14 @@ class PagesTranslations implements Translations {
@override @override
Map<String, Map<String, String>> get keys => { Map<String, Map<String, String>> get keys => {
'en': { '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', 'schedule_an_appointment': 'Appointment',
'There are no notifications to display at this time.': 'There are no notifications to display at this time.':
'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.', 'Enter your email to reset your password please \n We will send verification code to your Email.',
}, },
'ar': { 'ar': {
'appointment': 'موعد',
'you_have_an_appointment_on': 'لديك موعد بتاريخ', 'you_have_an_appointment_on': 'لديك موعد بتاريخ',
'appointment_scheduled_successfully.': 'تم حجز الموعد بنجاح',
'am': 'صباحاً',
'pm': 'مساءً',
'available_times': 'الأوقات المتاحة',
'you_have_an_appointment': 'التفاصيل',
'schedule_an_appointment': 'حجز موعد', 'schedule_an_appointment': 'حجز موعد',
'There are no notifications to display at this time.': 'There are no notifications to display at this time.':
'لا توجد إشعارات لعرضها في الوقت الحالي.', 'لا توجد إشعارات لعرضها في الوقت الحالي.',

View File

@ -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<List<AssetEntity>?> pickImage(BuildContext context) async { static Future<List<AssetEntity>?> pickImage(BuildContext context) async {
return await AssetPicker.pickAssets( return await AssetPicker.pickAssets(
context, context,

View File

@ -238,4 +238,12 @@ class CardController extends GetxController {
onError?.call(e); onError?.call(e);
}, onSuccess: onSuccess); }, onSuccess: onSuccess);
} }
/// ------------ card appointments ---------///
RxFuture<void> availableAppointmentsState = RxFuture(null);
void getAvailableAppointments() async {
return availableAppointmentsState.observe((value) async {
await Future.delayed(const Duration(seconds: 3));
});
}
} }

View File

@ -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/button.dart';
import 'package:taafee_mobile/common/widgets/text.dart'; import 'package:taafee_mobile/common/widgets/text.dart';
import 'package:taafee_mobile/core/routing/routing_manager.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/business_logic_layer/card_controller.dart';
import 'package:taafee_mobile/features/card/data_layer/model/appointment.dart'; import 'package:taafee_mobile/features/card/data_layer/model/appointment.dart';
import 'package:taafee_mobile/features/card/presentation_layer/widgets/card_details.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 '../../../../core/url launcher/url_launcher_service.dart';
import '../../../home/business_logic_layer/home_controller.dart'; import '../../../home/business_logic_layer/home_controller.dart';
import '../../data_layer/model/card_model.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_email.dart';
import '../widgets/card_header.dart'; import '../widgets/card_header.dart';
import '../widgets/card_image.dart'; import '../widgets/card_image.dart';
@ -30,6 +33,93 @@ class CardDetailsScreen extends StatelessWidget {
final ChatController chatController = Get.find<ChatController>(); final ChatController chatController = Get.find<ChatController>();
@override @override
Widget build(BuildContext context) { 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); cardController.updateCardNetworkImageUrls(cardModel.cardImages);
return Scaffold( return Scaffold(
backgroundColor: AppColors.backGroundColor, backgroundColor: AppColors.backGroundColor,
@ -262,18 +352,32 @@ class CardDetailsScreen extends StatelessWidget {
}), }),
), ),
if (Responsive.isTablet()) if (Responsive.isTablet())
Visibility( Obx(() {
return Visibility(
visible: homeController.user.value!.id != cardModel.user.id, visible: homeController.user.value!.id != cardModel.user.id,
child: ButtonWidget( child: (homeController.currentCardAppointment.value != null)
? ButtonWidget(
haveIcon: true,
onTap: () { onTap: () {
homeController.scheduleAnAppointment( appointmentDialog(context);
Appointment( },
cardId: cardModel.id, fontSize: 12,
dateTime: DateTime.now(), color: AppColors.secondaryColor,
user: homeController.user.value!), 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, haveIcon: true,
fontSize: 12,
title: 'schedule_an_appointment'.tr, title: 'schedule_an_appointment'.tr,
color: AppColors.secondaryColor, color: AppColors.secondaryColor,
child: const Icon( child: const Icon(
@ -281,9 +385,10 @@ class CardDetailsScreen extends StatelessWidget {
color: Colors.white, color: Colors.white,
), ),
) )
.paddingSymmetric(horizontal: 10) .paddingSymmetric(horizontal: 4)
.expanded(Responsive.isTablet() ? 1 : 5), .expanded(Responsive.isTablet() ? 1 : 5),
), );
}),
], ],
).paddingSymmetric( ).paddingSymmetric(
vertical: Responsive.isTablet() ? 20 : 4, vertical: Responsive.isTablet() ? 20 : 4,
@ -299,22 +404,24 @@ class CardDetailsScreen extends StatelessWidget {
child: child:
(homeController.currentCardAppointment.value != null) (homeController.currentCardAppointment.value != null)
? ButtonWidget( ? ButtonWidget(
onTap: () {}, haveIcon: true,
onTap: () {
appointmentDialog(context);
},
fontSize: 12, fontSize: 12,
color: AppColors.secondaryColor, color: AppColors.secondaryColor,
title: title: 'you_have_an_appointment'.tr,
'${'you_have_an_appointment_on'.tr} ${homeController.currentCardAppointment.value!.dateTime.toString().substring(0, 11)}', child: const Icon(
Icons.schedule,
color: Colors.white,
),
) )
.paddingSymmetric(horizontal: 4) .paddingSymmetric(horizontal: 4)
.expanded(Responsive.isTablet() ? 1 : 5) .expanded(Responsive.isTablet() ? 1 : 5)
: ButtonWidget( : ButtonWidget(
onTap: () { onTap: () {
homeController.scheduleAnAppointment( cardController.getAvailableAppointments();
Appointment( appointmentSchedulingDialog(context);
cardId: cardModel.id,
dateTime: DateTime.now(),
user: homeController.user.value!),
);
}, },
haveIcon: true, haveIcon: true,
fontSize: 12, fontSize: 12,

View File

@ -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<HomeController>();
@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)
],
),
);
}
}

View File

@ -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<HomeController>();
@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,
),
]),
),
);
}
}

View File

@ -182,7 +182,14 @@ class HomeController extends GetxController {
user.refresh(); user.refresh();
} }
void scheduleAnAppointment(Appointment appointment) async { RxFuture<void> 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) { user.update((val) {
if (val!.appointments != null) { if (val!.appointments != null) {
val.appointments!.add(appointment); val.appointments!.add(appointment);
@ -197,6 +204,9 @@ class HomeController extends GetxController {
currentCardAppointment = appointment.obs; currentCardAppointment = appointment.obs;
currentCardAppointment.refresh(); currentCardAppointment.refresh();
user.refresh(); user.refresh();
onSuccess?.call();
},
);
} }
Appointment? getCardAppointment(int cardId) { Appointment? getCardAppointment(int cardId) {