From e00f0354dd59321d363c54b07488ddbaa52add0a Mon Sep 17 00:00:00 2001 From: MhdZiadHirati Date: Thu, 19 Oct 2023 17:26:07 +0300 Subject: [PATCH] appointments ui and caching --- lib/common/widgets/button.dart | 7 + lib/core/local_storage/local_storage.dart | 29 ++++ lib/core/localization/localization.dart | 6 +- .../presentation_layer/screens/account.dart | 2 +- lib/features/auth/data_layer/model/user.dart | 4 + .../business_logic_layer/card_controller.dart | 2 + .../card/data_layer/model/appointment.dart | 41 ++++++ .../screens/card_details.dart | 134 ++++++++++++++---- .../widgets/first_card.dart | 6 +- .../presentation_layer/widgets/my_card.dart | 1 + .../widgets/second_card.dart | 5 +- .../presentation_layer/screens/favorite.dart | 80 ++++++----- .../business_logic_layer/home_controller.dart | 51 ++++++- 13 files changed, 292 insertions(+), 76 deletions(-) create mode 100644 lib/features/card/data_layer/model/appointment.dart diff --git a/lib/common/widgets/button.dart b/lib/common/widgets/button.dart index 48d1077..056c4c2 100644 --- a/lib/common/widgets/button.dart +++ b/lib/common/widgets/button.dart @@ -16,6 +16,7 @@ class ButtonWidget extends StatelessWidget { final bool? isLoading; final double? buttonHeight; final bool hideTextOnLoading; + final double? fontSize; const ButtonWidget( {super.key, required this.onTap, @@ -27,6 +28,7 @@ class ButtonWidget extends StatelessWidget { this.loaderColor, this.width, this.buttonHeight, + this.fontSize, this.textColor, this.isLoading = false}); @@ -44,6 +46,7 @@ class ButtonWidget extends StatelessWidget { child ?? Container(), BoldTextWidget( title, + fontSize: fontSize, color: Colors.white, ).paddingSymmetric(horizontal: 10), ]) @@ -58,6 +61,8 @@ class ButtonWidget extends StatelessWidget { isLoading!), child: BoldTextWidget( title, + fontSize: fontSize, + textAlign: TextAlign.center, color: Colors.white, ).paddingSymmetric(horizontal: 20), ), @@ -72,6 +77,8 @@ class ButtonWidget extends StatelessWidget { ) : BoldTextWidget( title, + fontSize: fontSize, + textAlign: TextAlign.center, color: Colors.white, ), ).onTap(() { diff --git a/lib/core/local_storage/local_storage.dart b/lib/core/local_storage/local_storage.dart index 5daa877..4710604 100644 --- a/lib/core/local_storage/local_storage.dart +++ b/lib/core/local_storage/local_storage.dart @@ -1,8 +1,10 @@ +import 'dart:convert'; import 'dart:developer'; import 'package:get_storage/get_storage.dart'; import '../../features/auth/data_layer/model/user.dart'; +import '../../features/card/data_layer/model/appointment.dart'; class LocalStorage { final GetStorage storage = GetStorage(); @@ -76,4 +78,31 @@ class LocalStorage { Future clearCache() async { await storage.erase(); } + + Future setAppointments(List appointments) async { + for (int i = 0; i < appointments.length; i++) { + await storage.write( + 'appointments[$i]', jsonEncode(appointments[i].toJson())); + } + await storage.write('appointments_length', appointments.length); + } + + List? getAppointments() { + int appointmentsLength = storage.read('appointments_length') ?? 0; + if (appointmentsLength == 0) { + return null; + } else { + List appointments = []; + for (int i = 0; i < appointmentsLength; i++) { + appointments.add( + Appointment.fromJson( + jsonDecode( + storage.read('appointments[$i]'), + ), + ), + ); + } + return appointments; + } + } } diff --git a/lib/core/localization/localization.dart b/lib/core/localization/localization.dart index b5dd03b..406fb9d 100644 --- a/lib/core/localization/localization.dart +++ b/lib/core/localization/localization.dart @@ -4,7 +4,8 @@ class PagesTranslations implements Translations { @override Map> get keys => { 'en': { - 'schedule_an_appointment': 'Schedule an appointment', + 'you_have_an_appointment_on': 'your appointment on', + 'schedule_an_appointment': 'Appointment', 'There are no notifications to display at this time.': 'There are no notifications to display at this time.', 'no_previous_conversations_!': 'No Previous Conversations !', @@ -183,7 +184,8 @@ class PagesTranslations implements Translations { 'Enter your email to reset your password please \n We will send verification code to your Email.', }, 'ar': { - 'schedule_an_appointment': 'جدولة موعد', + 'you_have_an_appointment_on': 'لديك موعد بتاريخ', + 'schedule_an_appointment': 'حجز موعد', 'There are no notifications to display at this time.': 'لا توجد إشعارات لعرضها في الوقت الحالي.', 'no_previous_conversations_!': 'لا توجد محادثات سابقة!', diff --git a/lib/features/account/presentation_layer/screens/account.dart b/lib/features/account/presentation_layer/screens/account.dart index af8fe0a..a57c8b8 100644 --- a/lib/features/account/presentation_layer/screens/account.dart +++ b/lib/features/account/presentation_layer/screens/account.dart @@ -33,7 +33,7 @@ class AccountScreen extends StatelessWidget { @override Widget build(BuildContext context) { accountController.getSelectedLanguageIcon(); - homeController.readUser(); + // homeController.readUser(); return Scaffold( backgroundColor: AppColors.backGroundColor, body: SingleChildScrollView( diff --git a/lib/features/auth/data_layer/model/user.dart b/lib/features/auth/data_layer/model/user.dart index 796a4f6..e823417 100644 --- a/lib/features/auth/data_layer/model/user.dart +++ b/lib/features/auth/data_layer/model/user.dart @@ -1,3 +1,5 @@ +import 'package:taafee_mobile/features/card/data_layer/model/appointment.dart'; + class User { int id; String firstName; @@ -5,6 +7,7 @@ class User { String email; int chatUserId; String? avatarImage; + List? appointments; User({ required this.id, required this.firstName, @@ -12,6 +15,7 @@ class User { required this.email, required this.avatarImage, required this.chatUserId, + this.appointments, }); factory User.fromJson(Map json) => User( diff --git a/lib/features/card/business_logic_layer/card_controller.dart b/lib/features/card/business_logic_layer/card_controller.dart index 2e06c4e..c61a4bd 100644 --- a/lib/features/card/business_logic_layer/card_controller.dart +++ b/lib/features/card/business_logic_layer/card_controller.dart @@ -5,6 +5,7 @@ import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:taafee_mobile/common/const/const.dart'; +import 'package:taafee_mobile/core/local_storage/local_storage.dart'; import 'package:taafee_mobile/features/card/data_layer/model/add_card.dart'; import 'package:taafee_mobile/features/card/data_layer/model/card_images.dart'; import 'package:taafee_mobile/features/card/data_layer/model/card_model.dart'; @@ -17,6 +18,7 @@ import '../../../core/utils/pagination_list.dart'; class CardController extends GetxController { //---------------data source-----------// CardService cardService = CardService(); + LocalStorage localStorage = LocalStorage(); //----------- model-----------// Rx cardModel = AddCardModel.zero().obs; diff --git a/lib/features/card/data_layer/model/appointment.dart b/lib/features/card/data_layer/model/appointment.dart new file mode 100644 index 0000000..2f37442 --- /dev/null +++ b/lib/features/card/data_layer/model/appointment.dart @@ -0,0 +1,41 @@ +import '../../../auth/data_layer/model/user.dart'; + +class Appointment { + DateTime dateTime; + int cardId; + User user; + Appointment( + {required this.cardId, required this.dateTime, required this.user}); + factory Appointment.fromJson(Map jsonMap) => Appointment( + cardId: jsonMap['card_id'], + dateTime: DateTime.parse(jsonMap['date']), + user: User.fromJson(jsonMap['user'])); + + Map toJson() => { + "card_id": cardId, + "date": dateTime.toString(), + "user": user.toJson(), + }; + static List fromJsonList(List jsonList) { + List appointments = []; + // ignore: avoid_function_literals_in_foreach_calls + jsonList.forEach((element) { + appointments.add(Appointment.fromJson(element)); + }); + return appointments; + } + + static List> toJsonList(List appointments) { + List> jsonList = []; + // ignore: avoid_function_literals_in_foreach_calls + appointments.forEach((element) { + jsonList.add(element.toJson()); + }); + return jsonList; + } + + @override + toString() { + return 'card_id:$cardId,userName:${user.firstName},date:${dateTime.toString()}'; + } +} diff --git a/lib/features/card/presentation_layer/screens/card_details.dart b/lib/features/card/presentation_layer/screens/card_details.dart index b60e49a..9319dc1 100644 --- a/lib/features/card/presentation_layer/screens/card_details.dart +++ b/lib/features/card/presentation_layer/screens/card_details.dart @@ -7,6 +7,7 @@ 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/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'; import 'package:taafee_mobile/features/card/presentation_layer/widgets/card_service.dart'; import 'package:taafee_mobile/features/chat/business%20logic%20layer/chat_controller.dart'; @@ -199,6 +200,9 @@ class CardDetailsScreen extends StatelessWidget { ], ), ), + const SizedBox( + height: 40, + ), Row( children: [ ButtonWidget( @@ -215,7 +219,7 @@ class CardDetailsScreen extends StatelessWidget { const ColorFilter.mode(Colors.white, BlendMode.srcIn), ), ) - .paddingSymmetric(horizontal: 10) + .paddingSymmetric(horizontal: 4) .expanded(Responsive.isTablet() ? 1 : 5), ButtonWidget( color: AppColors.emailColor, @@ -227,7 +231,7 @@ class CardDetailsScreen extends StatelessWidget { textColor: Colors.white, child: SvgPicture.asset("assets/icons/Email.svg"), ) - .paddingSymmetric(horizontal: 10) + .paddingSymmetric(horizontal: 4) .expanded(Responsive.isTablet() ? 1 : 5), if (Responsive.isTablet()) Visibility( @@ -257,40 +261,110 @@ class CardDetailsScreen extends StatelessWidget { ).expanded(2); }), ), + 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), + ), ], ).paddingSymmetric( - vertical: Responsive.isTablet() ? 20 : 10, + vertical: Responsive.isTablet() ? 20 : 4, horizontal: Responsive.isTablet() ? 40 : 10), if (!Responsive.isTablet()) - Visibility( - visible: chatController.chatUser.id != cardModel.user.chatUserId, - child: Obx(() { - return ButtonWidget( - isLoading: chatController.createRoomState.loading, - textColor: Colors.white, - color: AppColors.messageColor, - haveIcon: true, - onTap: () { - if (chatController.connectionState.value == - SocketConnectionState.connected) { - chatController.createRoom( - chatUserId: cardModel.user.chatUserId, - onSuccess: (room) { - RoutingManager.to(RouteName.chatDetails, - arguments: room); - }); - } else { - Toast.showToast('you_have_no_internet_connection'.tr); - } - }, - title: "start_conversation".tr, - child: SvgPicture.asset("assets/icons/message.svg"), - ); - }), - ), + Obx(() { + return Row( + children: [ + Obx(() { + return Visibility( + visible: + homeController.user.value!.id != cardModel.user.id, + child: + (homeController.currentCardAppointment.value != null) + ? ButtonWidget( + onTap: () {}, + fontSize: 12, + color: AppColors.secondaryColor, + title: + '${'you_have_an_appointment_on'.tr} ${homeController.currentCardAppointment.value!.dateTime.toString().substring(0, 11)}', + ) + .paddingSymmetric(horizontal: 4) + .expanded(Responsive.isTablet() ? 1 : 5) + : ButtonWidget( + onTap: () { + homeController.scheduleAnAppointment( + Appointment( + cardId: cardModel.id, + dateTime: DateTime.now(), + user: homeController.user.value!), + ); + }, + 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), + ); + }), + Visibility( + visible: + chatController.chatUser.id != cardModel.user.chatUserId, + child: ButtonWidget( + isLoading: chatController.createRoomState.loading, + textColor: Colors.white, + color: AppColors.messageColor, + haveIcon: true, + onTap: () { + if (chatController.connectionState.value == + SocketConnectionState.connected) { + chatController.createRoom( + chatUserId: cardModel.user.chatUserId, + onSuccess: (room) { + RoutingManager.to(RouteName.chatDetails, + arguments: room); + }); + } else { + Toast.showToast('you_have_no_internet_connection'.tr); + } + }, + title: "start_conversation".tr, + fontSize: 12, + child: SvgPicture.asset("assets/icons/message.svg"), + ) + .paddingSymmetric(horizontal: 4) + .expanded(Responsive.isTablet() ? 1 : 5), + ), + ], + ).paddingSymmetric( + vertical: Responsive.isTablet() ? 20 : 4, + horizontal: Responsive.isTablet() ? 40 : 10); + }), const SizedBox( height: 60, - ) + ), ], ).paddingSymmetric(horizontal: Responsive.isTablet() ? 20 : 0)), ).makeSafeArea(); diff --git a/lib/features/card/presentation_layer/widgets/first_card.dart b/lib/features/card/presentation_layer/widgets/first_card.dart index 7964bfa..c4ce529 100644 --- a/lib/features/card/presentation_layer/widgets/first_card.dart +++ b/lib/features/card/presentation_layer/widgets/first_card.dart @@ -7,14 +7,15 @@ import 'package:taafee_mobile/common/extensions/widget_extension.dart'; import 'package:taafee_mobile/common/widgets/text.dart'; import 'package:taafee_mobile/features/card/data_layer/model/card_model.dart'; import 'package:taafee_mobile/features/card/presentation_layer/widgets/card_location.dart'; +import 'package:taafee_mobile/features/home/business_logic_layer/home_controller.dart'; import '../../../../core/routing/routing_manager.dart'; import 'card_header.dart'; class FirstCardWidget extends StatelessWidget { final CardModel cardModel; - const FirstCardWidget(this.cardModel, {super.key}); - + final HomeController homeController = Get.find(); + FirstCardWidget(this.cardModel, {super.key}); @override Widget build(BuildContext context) { return Container( @@ -95,6 +96,7 @@ class FirstCardWidget extends StatelessWidget { ), ], ).onTap(() { + homeController.setCardAppointment(cardModel.id); RoutingManager.to(RouteName.cardDetails, arguments: cardModel); }), ], diff --git a/lib/features/card/presentation_layer/widgets/my_card.dart b/lib/features/card/presentation_layer/widgets/my_card.dart index 73191e3..186f64d 100644 --- a/lib/features/card/presentation_layer/widgets/my_card.dart +++ b/lib/features/card/presentation_layer/widgets/my_card.dart @@ -70,6 +70,7 @@ class MyCardWidget extends StatelessWidget { ), ], ).onTap(() { + homeController.setCardAppointment(cardModel.id); RoutingManager.to(RouteName.cardDetails, arguments: cardModel); }), ], diff --git a/lib/features/card/presentation_layer/widgets/second_card.dart b/lib/features/card/presentation_layer/widgets/second_card.dart index 9246cb0..641a2ff 100644 --- a/lib/features/card/presentation_layer/widgets/second_card.dart +++ b/lib/features/card/presentation_layer/widgets/second_card.dart @@ -6,13 +6,15 @@ import 'package:taafee_mobile/common/extensions/widget_extension.dart'; import 'package:taafee_mobile/common/widgets/text.dart'; import 'package:taafee_mobile/features/card/data_layer/model/card_model.dart'; import 'package:taafee_mobile/features/card/presentation_layer/widgets/card_header.dart'; +import 'package:taafee_mobile/features/home/business_logic_layer/home_controller.dart'; import '../../../../common/const/const.dart'; import '../../../../core/routing/routing_manager.dart'; class SecondCardWidget extends StatelessWidget { final CardModel cardModel; - const SecondCardWidget(this.cardModel, {super.key}); + final HomeController homeController = Get.find(); + SecondCardWidget(this.cardModel, {super.key}); @override Widget build(BuildContext context) { @@ -92,6 +94,7 @@ class SecondCardWidget extends StatelessWidget { ).paddingSymmetric(horizontal: 5), ], ).onTap(() { + homeController.setCardAppointment(cardModel.id); RoutingManager.to(RouteName.cardDetails, arguments: cardModel); }), ), diff --git a/lib/features/favorite/presentation_layer/screens/favorite.dart b/lib/features/favorite/presentation_layer/screens/favorite.dart index e89c203..6810c58 100644 --- a/lib/features/favorite/presentation_layer/screens/favorite.dart +++ b/lib/features/favorite/presentation_layer/screens/favorite.dart @@ -11,10 +11,12 @@ import 'package:taafee_mobile/core/routing/routing_manager.dart'; import 'package:taafee_mobile/features/card/business_logic_layer/card_controller.dart'; import 'package:taafee_mobile/features/favorite/business_logic_layer/favorite_controller.dart'; import 'package:taafee_mobile/features/favorite/presentation_layer/widgets/favorite_card.dart'; +import 'package:taafee_mobile/features/home/business_logic_layer/home_controller.dart'; class FavoriteScreen extends StatelessWidget { final CardController cardController = Get.find(); final FavoriteController favoriteController = Get.find(); + final HomeController homeController = Get.find(); FavoriteScreen({super.key}); @override @@ -41,44 +43,46 @@ class FavoriteScreen extends StatelessWidget { width: Get.width, height: Get.height * 0.75, rxFuture: favoriteController.getFavoriteState, - child: () => - (favoriteController.getFavoriteState.result.isNotEmpty) - ? GridViewWidget( - mainAxisExtent: 150, - count: Responsive.isTablet() ? 3 : 2, - itemCount: - favoriteController.getFavoriteState.result.length, - child: (index) => Obx(() { - return FavoriteCardWidget( - favoriteModel: favoriteController - .getFavoriteState.result[index], - ).onTap(() { - RoutingManager.to(RouteName.cardDetails, - arguments: favoriteController.getFavoriteState - .result[index].cardModel); - }); - }), - ) - : SizedBox( - height: Get.height * 0.75, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 128, - height: 128, - child: Lottie.asset( - 'assets/animations/NO Favorite.json', - repeat: false, - ), - ), - RegularTextWidget( - 'add_some_favorite_cards !'.tr, - fontSize: Responsive.isTablet() ? 18 : 16, - ), - ], - ).center(), - ), + child: () => (favoriteController + .getFavoriteState.result.isNotEmpty) + ? GridViewWidget( + mainAxisExtent: 150, + count: Responsive.isTablet() ? 3 : 2, + itemCount: + favoriteController.getFavoriteState.result.length, + child: (index) => Obx(() { + return FavoriteCardWidget( + favoriteModel: + favoriteController.getFavoriteState.result[index], + ).onTap(() { + homeController.setCardAppointment(favoriteController + .getFavoriteState.result[index].cardModel.id); + RoutingManager.to(RouteName.cardDetails, + arguments: favoriteController + .getFavoriteState.result[index].cardModel); + }); + }), + ) + : SizedBox( + height: Get.height * 0.75, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 128, + height: 128, + child: Lottie.asset( + 'assets/animations/NO Favorite.json', + repeat: false, + ), + ), + RegularTextWidget( + 'add_some_favorite_cards !'.tr, + fontSize: Responsive.isTablet() ? 18 : 16, + ), + ], + ).center(), + ), ) // const ListViewWidget( // itemCount: 8, diff --git a/lib/features/home/business_logic_layer/home_controller.dart b/lib/features/home/business_logic_layer/home_controller.dart index 14ea9e4..7533c9a 100644 --- a/lib/features/home/business_logic_layer/home_controller.dart +++ b/lib/features/home/business_logic_layer/home_controller.dart @@ -6,6 +6,7 @@ import 'package:taafee_mobile/common/widgets/button.dart'; import 'package:taafee_mobile/common/widgets/text.dart'; import 'package:taafee_mobile/core/local_storage/local_storage.dart'; import 'package:taafee_mobile/core/routing/routing_manager.dart'; +import 'package:taafee_mobile/features/card/data_layer/model/appointment.dart'; import 'package:taafee_mobile/features/card/data_layer/model/card_model.dart'; import 'package:taafee_mobile/features/home/data_layer/model/city.dart'; import 'package:taafee_mobile/features/home/data_layer/model/search.dart'; @@ -50,7 +51,6 @@ class HomeController extends GetxController { } //------------read user----------//// - //pick avatar image Rx pickedUserImage = null.obs; RxBool isAvatarImagePicked = false.obs; @@ -64,7 +64,12 @@ class HomeController extends GetxController { } else { isUserHasAvatar.value = false; } - + List? appointments = storage.getAppointments(); + if (appointments != null) { + user.update((val) { + val!.appointments = appointments; + }); + } user.refresh(); isUserHasAvatar.refresh(); } @@ -167,4 +172,46 @@ class HomeController extends GetxController { } isArabic.refresh(); } + + /// ----------- schedule an appointment -------/// + Rx currentCardAppointment = null.obs; + + void setCardAppointment(int cardId) { + currentCardAppointment = getCardAppointment(cardId).obs; + currentCardAppointment.refresh(); + 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); + } + }); + + try { + await storage.setAppointments(user.value!.appointments!); + print(user.value!.appointments); + } catch (error) { + print(error.toString()); + } + currentCardAppointment = appointment.obs; + currentCardAppointment.refresh(); + print('currentCardAppointment:${currentCardAppointment.value}'); + user.refresh(); + } + + Appointment? getCardAppointment(int cardId) { + if (user.value!.appointments == null || user.value!.appointments!.isEmpty) { + return null; + } + print('user appointments: ${user.value!.appointments!}'); + Appointment? appointment = user.value!.appointments! + .firstWhereOrNull((element) => (element.cardId == cardId)); + print('current appointment:$appointment'); + return appointment; + } }