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 {
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);

View File

@ -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,

View File

@ -4,7 +4,14 @@ class PagesTranslations implements Translations {
@override
Map<String, Map<String, String>> 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.':
'لا توجد إشعارات لعرضها في الوقت الحالي.',

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 {
return await AssetPicker.pickAssets(
context,

View File

@ -238,4 +238,12 @@ class CardController extends GetxController {
onError?.call(e);
}, 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/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<ChatController>();
@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,

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,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<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) {
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) {