diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 6a96725..0e1df59 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -665,11 +665,19 @@ "itemName": "اسم العنصر", "itemDescription": "وصف العنصر", "itemPrice": "سعر العنصر", + "bookAppointmentForServices": "سيسمح هذا الخيار للعميل بحجز موعد لهذه الخدمات.", + "showServiceAvailability": "سيظهر هذا الخيار للعميل ما إذا كان بإمكانه الحصول على هذه الخدمة في الورشة أم لا.", + "bookAppointmentAtLocation": "سيسمح هذا الخيار للعميل بحجز موعد في الموقع الذي يرغب فيه.", "appointmentBookingOption": "سيسمح هذا الخيار للعميل بحجز موعد لهذه الخدمات.", "workshopAvailabilityOption": "سيظهر هذا الخيار للعميل إذا كان يمكنه الحصول على هذه الخدمة في الورشة أم لا.", "appointmentLocationOption": "سيسمح هذا الخيار للعميل بحجز موعد في الموقع الذي يختاره.", "deleteScheduleConfirmation": "هل أنت متأكد أنك تريد حذف هذا الجدول؟", "deleteScheduleAdConfirmationMessage": "سيتم إزالة جميع فترات المواعيد ولن يتمكن العملاء من حجز مواعيد لهذا الجدول.", "branchSchedules": "جداول الفرع", - "noSchedulesFound": "لم تقم بإضافة أي جدول لهذا الفرع." + "noSchedulesFound": "لم تقم بإضافة أي جدول لهذا الفرع.", + "inviteFriendsBySMS": "دعوة الأصدقاء عبر الرسائل القصيرة", + "inviteFriendsByWhatsApp": "دعوة الأصدقاء عبر واتساب", + "inviteFriendsByEmail": "دعوة الأصدقاء عبر البريد الإلكتروني", + "noFAQsToShow": "لا توجد أسئلة شائعة في الوقت الحالي. يرجى الاتصال بنا إذا كان لديك أي استفسار.", + "appInfo": "معلومات التطبيق" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 3d26af4..a4cf2bd 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -672,5 +672,10 @@ "deleteScheduleConfirmation": " Are you sure you want to delete this Schedule?", "deleteScheduleAdConfirmationMessage": "All the appointment slots will be removed and customers will no longer be able to book appointments for this schedule.", "branchSchedules": "Branch Schedules", - "noSchedulesFound": "You have not added any schedule for this branch." + "noSchedulesFound": "You have not added any schedule for this branch.", + "inviteFriendsBySMS": "Invite Friends By SMS", + "inviteFriendsByWhatsApp": "Invite Friends By WhatsApp", + "inviteFriendsByEmail": "Invite Friends By Email", + "noFAQsToShow": "There are no Frequently asked Questions Right now. Please contact us if you have any query.", + "appInfo": "App Info" } \ No newline at end of file diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 9e3e565..59fd5a2 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -174,6 +174,12 @@ class ApiConsts { static String getChatMessagesForAds = "${baseUrlServices}api/Advertisement/AdsChat_Get"; static String getChatBuyersForAds = "${baseUrlServices}api/Advertisement/AdsChatBuyer_Get"; + //Settings Options + static String getAllFAQs = "${baseUrlServices}api/Common/FAQ_Get"; + static String getAppInvitationLink = "${baseUrlServices}api/Common/AppInvitation_Get"; + static String getContactInfo = "${baseUrlServices}api/Master/ContactInfo_Get"; + static String getAppInfo = "${baseUrlServices}api/Master/AppInfo_Get"; + static List closingUrls = ["PayFortResponse"]; } diff --git a/lib/config/dependency_injection.dart b/lib/config/dependency_injection.dart index f3c8745..8a42acd 100644 --- a/lib/config/dependency_injection.dart +++ b/lib/config/dependency_injection.dart @@ -12,6 +12,7 @@ import 'package:mc_common_app/repositories/branch_repo.dart'; import 'package:mc_common_app/repositories/chat_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/repositories/payments_repo.dart'; +import 'package:mc_common_app/repositories/setting_options_repo.dart'; import 'package:mc_common_app/repositories/shipping_repo.dart'; import 'package:mc_common_app/repositories/user_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; @@ -44,6 +45,7 @@ class AppDependencies { injector.registerSingleton(() => ChatRepoImp()); injector.registerSingleton(() => BranchRepoImp()); injector.registerSingleton(() => ShippingRepoImp()); + injector.registerSingleton(() => SettingOptionsRepoImp()); // } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 515974b..ea4978e 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -20,9 +20,11 @@ import 'package:mc_common_app/views/requests/requests_filter_view.dart'; import 'package:mc_common_app/views/requests/review_request_offer.dart'; import 'package:mc_common_app/views/setting_options/provider_license_page.dart'; import 'package:mc_common_app/views/setting_options/setting_option_help.dart'; +import 'package:mc_common_app/views/setting_options/setting_options_app_info.dart'; +import 'package:mc_common_app/views/setting_options/setting_options_contact_us.dart'; import 'package:mc_common_app/views/setting_options/setting_options_faqs.dart'; import 'package:mc_common_app/views/setting_options/setting_options_invite_friends.dart'; -import 'package:mc_common_app/views/setting_options/setting_options_language.dart'; +import 'package:mc_common_app/views/setting_options/setting_options_more.dart'; import 'package:mc_common_app/views/shipping_management/shipping_management_view.dart'; import 'package:mc_common_app/views/user/change_email_page.dart'; import 'package:mc_common_app/views/user/change_mobile_page.dart'; @@ -113,7 +115,9 @@ class AppRoutes { //Setting Options static const String settingOptionsFaqs = "/settingOptionsFaqs"; - static const String settingOptionsLanguages = "/settingOptionsLanguages"; + static const String settingOptionsContactUs = "/settingOptionsContactUs"; + static const String settingOptionsAppInfo = "/settingOptionsAppInfo"; + static const String settingOptionsMore = "/settingOptionsLanguages"; static const String settingOptionsInviteFriends = "/settingOptionsInviteFriends"; static const String settingOptionsHelp = "/settingOptionsHelp"; @@ -128,9 +132,12 @@ class AppRoutes { //Provider User Page static const dealerUser = "/dealerUser"; + // HomePage Provider + + static const String generalChatsListForProvider = "/generalChatsListForProvider"; + //Chat static const String chatView = "/chatView"; - static const String initialRoute = splash; static final Map routes = { @@ -153,7 +160,7 @@ class AppRoutes { changePassword: (context) => const ChangePasswordPage(), editAccountPage: (context) => const EditAccountPage(), profileView: (context) => const ProfileScreen(), - settingOptionsLanguages: (context) => const SettingOptionsLanguage(), + settingOptionsMore: (context) => const SettingOptionsMore(), settingOptionsHelp: (context) => const SettingOptionsHelp(), providerLicensePage: (context) => const ProviderLicensePage(), @@ -165,6 +172,8 @@ class AppRoutes { AppRoutes.chatView: (context) => ChatView(chatViewArguments: ModalRoute.of(context)!.settings.arguments as ChatViewArguments), AppRoutes.adsBuyerChatsListView: (context) => AdsBuyerChatsView(buyersListViewArguments: ModalRoute.of(context)!.settings.arguments as List), AppRoutes.settingOptionsFaqs: (context) => const SettingOptionsFAQs(), + AppRoutes.settingOptionsContactUs: (context) => const SettingOptionsContactUs(), + AppRoutes.settingOptionsAppInfo: (context) => const SettingOptionsAppInfo(), AppRoutes.settingOptionsInviteFriends: (context) => const SettingOptionsInviteFriends(), AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(paymentType: ModalRoute.of(context)!.settings.arguments as PaymentTypes), //Requests @@ -177,6 +186,9 @@ class AppRoutes { //MediaViewer AppRoutes.mediaViewerScreen: (context) => MediaViewerScreen(images: ModalRoute.of(context)!.settings.arguments as List), + // ChatsList Provider + AppRoutes.generalChatsListForProvider: (context) => OfferListPage(offerListPageArguments: ModalRoute.of(context)!.settings.arguments as OfferListPageArguments), + //Shipping AppRoutes.shippingManagementView: (context) => const ShippingManagementView(), }; diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 591c656..5e42c42 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -681,13 +681,21 @@ class CodegenLoader extends AssetLoader{ "itemName": "اسم العنصر", "itemDescription": "وصف العنصر", "itemPrice": "سعر العنصر", + "bookAppointmentForServices": "سيسمح هذا الخيار للعميل بحجز موعد لهذه الخدمات.", + "showServiceAvailability": "سيظهر هذا الخيار للعميل ما إذا كان بإمكانه الحصول على هذه الخدمة في الورشة أم لا.", + "bookAppointmentAtLocation": "سيسمح هذا الخيار للعميل بحجز موعد في الموقع الذي يرغب فيه.", "appointmentBookingOption": "سيسمح هذا الخيار للعميل بحجز موعد لهذه الخدمات.", "workshopAvailabilityOption": "سيظهر هذا الخيار للعميل إذا كان يمكنه الحصول على هذه الخدمة في الورشة أم لا.", "appointmentLocationOption": "سيسمح هذا الخيار للعميل بحجز موعد في الموقع الذي يختاره.", "deleteScheduleConfirmation": "هل أنت متأكد أنك تريد حذف هذا الجدول؟", "deleteScheduleAdConfirmationMessage": "سيتم إزالة جميع فترات المواعيد ولن يتمكن العملاء من حجز مواعيد لهذا الجدول.", "branchSchedules": "جداول الفرع", - "noSchedulesFound": "لم تقم بإضافة أي جدول لهذا الفرع." + "noSchedulesFound": "لم تقم بإضافة أي جدول لهذا الفرع.", + "inviteFriendsBySMS": "دعوة الأصدقاء عبر الرسائل القصيرة", + "inviteFriendsByWhatsApp": "دعوة الأصدقاء عبر واتساب", + "inviteFriendsByEmail": "دعوة الأصدقاء عبر البريد الإلكتروني", + "noFAQsToShow": "لا توجد أسئلة شائعة في الوقت الحالي. يرجى الاتصال بنا إذا كان لديك أي استفسار.", + "appInfo": "معلومات التطبيق" }; static const Map en_US = { "firstTimeLogIn": "First Time Log In", @@ -1363,7 +1371,12 @@ static const Map en_US = { "deleteScheduleConfirmation": " Are you sure you want to delete this Schedule?", "deleteScheduleAdConfirmationMessage": "All the appointment slots will be removed and customers will no longer be able to book appointments for this schedule.", "branchSchedules": "Branch Schedules", - "noSchedulesFound": "You have not added any schedule for this branch." + "noSchedulesFound": "You have not added any schedule for this branch.", + "inviteFriendsBySMS": "Invite Friends By SMS", + "inviteFriendsByWhatsApp": "Invite Friends By WhatsApp", + "inviteFriendsByEmail": "Invite Friends By Email", + "noFAQsToShow": "There are no Frequently asked Questions Right now. Please contact us if you have any query.", + "appInfo": "App Info" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 2e0162c..9734eae 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1,6 +1,6 @@ // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart -abstract class LocaleKeys { +abstract class LocaleKeys { static const firstTimeLogIn = 'firstTimeLogIn'; static const signUp = 'signUp'; static const changeMobile = 'changeMobile'; @@ -47,7 +47,7 @@ abstract class LocaleKeys { static const emailChangedSuccessfully = 'emailChangedSuccessfully'; static const passwordIsUpdated = 'passwordIsUpdated'; static const passwordShouldContains = 'passwordShouldContains'; - static const successfullyRegistered = ' successfullyRegistered'; + static const successfullyRegistered = ' successfullyRegistered'; static const pleaseEnterSamePassword = 'pleaseEnterSamePassword'; static const firstNameMandatory = 'firstNameMandatory'; static const surnameNameMandatory = 'surnameNameMandatory'; @@ -644,6 +644,9 @@ abstract class LocaleKeys { static const itemName = 'itemName'; static const itemDescription = 'itemDescription'; static const itemPrice = 'itemPrice'; + static const bookAppointmentForServices = 'bookAppointmentForServices'; + static const showServiceAvailability = 'showServiceAvailability'; + static const bookAppointmentAtLocation = 'bookAppointmentAtLocation'; static const appointmentBookingOption = 'appointmentBookingOption'; static const workshopAvailabilityOption = 'workshopAvailabilityOption'; static const appointmentLocationOption = 'appointmentLocationOption'; @@ -651,8 +654,10 @@ abstract class LocaleKeys { static const deleteScheduleAdConfirmationMessage = 'deleteScheduleAdConfirmationMessage'; static const branchSchedules = 'branchSchedules'; static const noSchedulesFound = 'noSchedulesFound'; - static const showServiceAvailability = 'showServiceAvailability'; - static const bookAppointmentForServices = 'bookAppointmentForServices'; - static const bookAppointmentAtLocation = 'bookAppointmentAtLocation'; + static const inviteFriendsBySMS = 'inviteFriendsBySMS'; + static const inviteFriendsByWhatsApp = 'inviteFriendsByWhatsApp'; + static const inviteFriendsByEmail = 'inviteFriendsByEmail'; + static const noFAQsToShow = 'noFAQsToShow'; + static const appInfo = 'appInfo'; } diff --git a/lib/models/provider_branches_models/profile/document.dart b/lib/models/provider_branches_models/profile/document.dart index 497e93b..eb6d2f8 100644 --- a/lib/models/provider_branches_models/profile/document.dart +++ b/lib/models/provider_branches_models/profile/document.dart @@ -55,9 +55,9 @@ class DocumentData { int? id; int? serviceProviderId; int? documentId; - dynamic? documentUrl; + String? documentUrl; int? status; - dynamic? comment; + String? comment; bool? isActive; String? document; String? fileExt; diff --git a/lib/models/setting_utils_models/app_info_model.dart b/lib/models/setting_utils_models/app_info_model.dart new file mode 100644 index 0000000..1bb141f --- /dev/null +++ b/lib/models/setting_utils_models/app_info_model.dart @@ -0,0 +1,26 @@ +import 'package:mc_common_app/view_models/ad_view_model.dart'; + +class AppInfoModel { + int? id; + String? header; + String? content; + bool? isActive; + int? channel; + List? appInfoImages; + + AppInfoModel({this.id, this.header, this.content, this.isActive, this.channel, this.appInfoImages}); + + AppInfoModel.fromJson(Map json) { + id = json['id']; + header = json['header']; + content = json['content']; + isActive = json['isActive']; + channel = json['channel']; + if (json['appInfoImages'] != null) { + appInfoImages = []; + json['appInfoImages'].forEach((v) { + appInfoImages!.add(ImageModel.fromJson(v)); + }); + } + } +} diff --git a/lib/models/setting_utils_models/contact_infos_model.dart b/lib/models/setting_utils_models/contact_infos_model.dart new file mode 100644 index 0000000..d7e6ea6 --- /dev/null +++ b/lib/models/setting_utils_models/contact_infos_model.dart @@ -0,0 +1,36 @@ +import 'package:mc_common_app/view_models/ad_view_model.dart'; + +class ContactInfoModel { + int? id; + String? companyName; + String? mobileNo; + String? phoneNo; + String? email; + String? address; + String? latitude; + String? longitude; + int? channel; + bool? isActive; + List? contactInfoImages; + + ContactInfoModel({this.id, this.companyName, this.mobileNo, this.phoneNo, this.email, this.address, this.latitude, this.longitude, this.channel, this.isActive, this.contactInfoImages}); + + ContactInfoModel.fromJson(Map json) { + id = json['id']; + companyName = json['companyName']; + mobileNo = json['mobileNo']; + phoneNo = json['phoneNo']; + email = json['email']; + address = json['address']; + latitude = json['latitude']; + longitude = json['longitude']; + channel = json['channel']; + isActive = json['isActive']; + if (json['contactInfoImages'] != null) { + contactInfoImages = []; + json['contactInfoImages'].forEach((v) { + contactInfoImages!.add(ImageModel.fromJson(v)); + }); + } + } +} diff --git a/lib/models/setting_utils_models/faqs_model.dart b/lib/models/setting_utils_models/faqs_model.dart new file mode 100644 index 0000000..6ee0944 --- /dev/null +++ b/lib/models/setting_utils_models/faqs_model.dart @@ -0,0 +1,32 @@ +class FAQsModel { + int? id; + String? question; + String? answer; + int? sequenceNo; + bool? isActive; + int? channel; + bool? isCollapsed; + + FAQsModel({this.id, this.question, this.answer, this.sequenceNo, this.isActive, this.channel, this.isCollapsed}); + + FAQsModel.fromJson(Map json) { + id = json['id']; + question = json['question']; + answer = json['answer']; + sequenceNo = json['sequenceNo']; + isActive = json['isActive']; + channel = json['channel']; + isCollapsed = false; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['question'] = question; + data['answer'] = answer; + data['sequenceNo'] = sequenceNo; + data['isActive'] = isActive; + data['channel'] = channel; + return data; + } +} diff --git a/lib/repositories/chat_repo.dart b/lib/repositories/chat_repo.dart index b23999f..f099937 100644 --- a/lib/repositories/chat_repo.dart +++ b/lib/repositories/chat_repo.dart @@ -33,6 +33,15 @@ abstract class ChatRepo { int pageIndex = 0, int pageSize = 0, }); + + Future> getUsersChatMessagesForGeneralChat({ + String? userID, + int? adID, + int? adsChatBuyerId, + required bool isForBuyer, + int pageIndex = 0, + int pageSize = 0, + }); } class ChatRepoImp implements ChatRepo { @@ -66,6 +75,8 @@ class ChatRepoImp implements ChatRepo { url = ApiConsts.messageIsReadUpdateForAds; } else if (chatTypeEnum == ChatTypeEnum.requestOffer) { url = ApiConsts.messageIsReadUpdateForRequests; + } else if (chatTypeEnum == ChatTypeEnum.general) { + url = ApiConsts.messageIsReadUpdateForRequests; } var queryParameters = {"iDs": messageIds}; GenericRespModel genericRespModel = await apiClient.postJsonForObject( @@ -144,4 +155,38 @@ class ChatRepoImp implements ChatRepo { } return []; } + + @override + Future> getUsersChatMessagesForGeneralChat({String? userID, int? adID, int? adsChatBuyerId, required bool isForBuyer, int pageIndex = 0, int pageSize = 0}) async { + var parameterForBuyer = { + "UserID": userID.toString(), + "AdsID": adID.toString(), + "IsAdsBuyer": isForBuyer.toString(), + "PageSize": pageSize.toString(), + "PageIndex": pageIndex.toString(), + }; + + var parameterForSeller = { + "AdsChatBuyerID": adsChatBuyerId.toString(), + "IsAdsBuyer": isForBuyer.toString(), + "PageSize": pageSize.toString(), + "PageIndex": pageIndex.toString(), + }; + + var queryParameters = isForBuyer ? parameterForBuyer : parameterForSeller; + GenericRespModel genericRespModel = await apiClient.getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getChatMessagesForAds, + queryParameters: queryParameters, + token: appState.getUser.data!.accessToken, + ); + if (genericRespModel.messageStatus == 1 && genericRespModel.data != null) { + List chatMessages = List.generate( + genericRespModel.data.length, + (index) => ChatMessageModel.fromJson(genericRespModel.data[index]), + ); + return chatMessages; + } + return []; + } } diff --git a/lib/repositories/setting_options_repo.dart b/lib/repositories/setting_options_repo.dart new file mode 100644 index 0000000..f4122ca --- /dev/null +++ b/lib/repositories/setting_options_repo.dart @@ -0,0 +1,84 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:mc_common_app/api/api_client.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/dependency_injection.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/app_info_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/contact_infos_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/faqs_model.dart'; +import 'package:mc_common_app/utils/utils.dart'; + +abstract class SettingOptionsRepo { + Future> getAllFaqs(); + + Future> getAllContactInfos(); + + Future> getAppInfoList(); +} + +class SettingOptionsRepoImp extends SettingOptionsRepo { + ApiClient apiClient = injector.get(); + AppState appState = injector.get(); + + @override + Future> getAllFaqs() async { + String token = appState.getUser.data!.accessToken ?? ""; + + GenericRespModel genericRespModel = await injector.get().getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getAllFAQs, + token: token, + ); + + if (genericRespModel.messageStatus != 1 || genericRespModel.data == null) { + Utils.showToast(genericRespModel.message ?? LocaleKeys.somethingWrong.tr()); + return []; + } + + List list = List.generate(genericRespModel.data.length, (index) => FAQsModel.fromJson(genericRespModel.data[index])); + + return list; + } + + @override + Future> getAllContactInfos() async { + String token = appState.getUser.data!.accessToken ?? ""; + + GenericRespModel genericRespModel = await injector.get().getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getContactInfo, + token: token, + ); + + if (genericRespModel.messageStatus != 1 || genericRespModel.data == null) { + Utils.showToast(genericRespModel.message ?? LocaleKeys.somethingWrong.tr()); + return []; + } + + List list = List.generate(genericRespModel.data.length, (index) => ContactInfoModel.fromJson(genericRespModel.data[index])); + + return list; + } + + @override + Future> getAppInfoList() async { + String token = appState.getUser.data!.accessToken ?? ""; + + GenericRespModel genericRespModel = await injector.get().getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getAppInfo, + token: token, + ); + + if (genericRespModel.messageStatus != 1 || genericRespModel.data == null) { + Utils.showToast(genericRespModel.message ?? LocaleKeys.somethingWrong.tr()); + return []; + } + + List list = List.generate(genericRespModel.data.length, (index) => AppInfoModel.fromJson(genericRespModel.data[index])); + + return list; + } +} diff --git a/lib/repositories/shipping_repo.dart b/lib/repositories/shipping_repo.dart index 6b99c6f..e1b0312 100644 --- a/lib/repositories/shipping_repo.dart +++ b/lib/repositories/shipping_repo.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; import 'package:mc_common_app/api/api_client.dart'; diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 8483fe2..1c33200 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -211,8 +211,9 @@ class AdVM extends BaseVM { notifyListeners(); return; } - + setState(ViewState.busy); exploreAdsFilteredList = await getAdsByFilter(createdByRoleEnum: createdByRoleFilter, isMyAds: false); + setState(ViewState.idle); notifyListeners(); } @@ -328,7 +329,7 @@ class AdVM extends BaseVM { return; } Utils.hideLoading(context); - Utils.showToast(respModel.message ?? "A has been marked as sold successfully!"); + Utils.showToast(respModel.message ?? "Ad has been marked as sold successfully!"); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.sold); //pending for review navigateReplaceWithName(context, AppRoutes.dashboard); @@ -1985,6 +1986,12 @@ class ImageModel { ImageModel({this.id, this.filePath, this.isFromNetwork}); + ImageModel.fromJson(Map json) { + id = json['id']; + filePath = json['image']; + isFromNetwork = true; + } + @override String toString() { return 'ImageModel{id: $id, isFromNetwork: $isFromNetwork, filePath: $filePath}'; diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 9b3a3ed..cf7ee78 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -1498,4 +1499,44 @@ class AppointmentsVM extends BaseVM { } setState(ViewState.idle); } + + // Chat Related + + Future getGeneralChatsListForProvider({required BuildContext context}) async { + try { + // Utils.showLoading(context); + // List buyersChatList = await appointmentRepo.getChatBuyersForAds(adsID: adsID); + // Utils.hideLoading(context); + // buyersChatListForAds.clear(); + // buyersChatListForAds = buyersChatList; + notifyListeners(); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + Utils.hideLoading(context); + } + } + + void onGeneralChatMessagesButtonPressed({required BuildContext context}) async { + final myUserID = AppState().getUser.data!.userInfo!.userId; + // if (adDetailsModel.isMyAd != null && adDetailsModel.isMyAd!) { + // await getGeneralChatsListForProvider(context: context); + // // navigateWithName(context, AppRoutes.generalChatsListForProvider, arguments: buyersChatListForAds); + // } else { + // ChatViewArgumentsForAd chatViewArgumentsForAd = ChatViewArgumentsForAd(receiverUserID: adDetailsModel.userID, adsID: adDetailsModel.id); + // ChatViewArguments chatViewArguments = ChatViewArguments(chatTypeEnum: ChatTypeEnum.ads, chatViewArgumentsForAd: chatViewArgumentsForAd); + // final chatVM = context.read(); + // await chatVM + // .getUsersChatMessagesForAd( + // context: context, + // isForBuyer: true, + // adsChatBuyerId: 1, + // adID: adDetailsModel.id, + // userID: myUserID, + // senderName: adDetailsModel.adOwnerName, + // ) + // .whenComplete(() => navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments)); + // } + } + } diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index d360063..2825c3a 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -193,14 +193,6 @@ class ChatVM extends ChangeNotifier { notifyListeners(); } - Future onNewMessageReceivedForAds({required List messages}) async { - for (var msg in messages) { - currentMessagesForAds.add(msg); - } - scrollChatDown(); - notifyListeners(); - } - void subscribeToReceiveRequestOfferMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (List? arguments) { if (arguments == null || arguments.isEmpty) return; @@ -215,18 +207,19 @@ class ChatVM extends ChangeNotifier { }); } - void subscribeToReceiveAdMessages(BuildContext context) { - hubConnection!.on(SignalrConsts.receiveMessageAds, (List? arguments) { - if (arguments == null || arguments.isEmpty) return; - List chat = []; - for (var message in arguments) { - final chatMessage = ChatMessageModel.fromJson(message as Map); - chat.add(chatMessage); - } - onNewMessageReceivedForAds(messages: chat); - logger.i(arguments); - // Utils.showToast(arguments.toString()); - }); + Future markMessagesAsRead(BuildContext context, List messageIds, ChatTypeEnum chatTypeEnum) async { + bool status = false; + try { + // Utils.showLoading(context); + status = await chatRepo.markMessageAsRead(messageIds: messageIds, chatTypeEnum: chatTypeEnum); + // Utils.hideLoading(context); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + // Utils.hideLoading(context); + status = false; + } + return status; } Future buildHubConnection(BuildContext context) async { @@ -236,6 +229,7 @@ class ChatVM extends ChangeNotifier { await hubConnection!.start(); subscribeToReceiveRequestOfferMessages(context); subscribeToReceiveAdMessages(context); + subscribeToReceiveGeneralMessages(context); hubConnection!.onclose((exception) { logger.i("onClose: ${exception.toString()}"); buildHubConnection(context); @@ -303,11 +297,7 @@ class ChatVM extends ChangeNotifier { MessageImageModel convertFileToMessageImageModel({required File file}) { List imageBytes = file.readAsBytesSync(); String image = base64Encode(imageBytes); - MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, - id: 0, - isFromNetwork: false, - reqOfferID: latestOfferId, - imagePath: file.path); + MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, id: 0, isFromNetwork: false, reqOfferID: latestOfferId, imagePath: file.path); return vehiclePostingImages; } @@ -413,10 +403,7 @@ class ChatVM extends ChangeNotifier { serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); } } else { - int providerIndex = context - .read() - .myFilteredRequests - .indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); + int providerIndex = context.read().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); log("providerIndex2:$providerIndex"); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex); @@ -615,19 +602,26 @@ class ChatVM extends ChangeNotifier { } } - Future markMessagesAsRead(BuildContext context, List messageIds, ChatTypeEnum chatTypeEnum) async { - bool status = false; - try { - // Utils.showLoading(context); - status = await chatRepo.markMessageAsRead(messageIds: messageIds, chatTypeEnum: chatTypeEnum); - // Utils.hideLoading(context); - } catch (e) { - logger.i(e.toString()); - Utils.showToast(e.toString()); - // Utils.hideLoading(context); - status = false; + void subscribeToReceiveAdMessages(BuildContext context) { + hubConnection!.on(SignalrConsts.receiveMessageAds, (List? arguments) { + if (arguments == null || arguments.isEmpty) return; + List chat = []; + for (var message in arguments) { + final chatMessage = ChatMessageModel.fromJson(message as Map); + chat.add(chatMessage); + } + onNewMessageReceivedForAds(messages: chat); + logger.i(arguments); + // Utils.showToast(arguments.toString()); + }); + } + + Future onNewMessageReceivedForAds({required List messages}) async { + for (var msg in messages) { + currentMessagesForAds.add(msg); } - return status; + scrollChatDown(); + notifyListeners(); } Future onTextMessageSendForAds({ @@ -679,7 +673,113 @@ class ChatVM extends ChangeNotifier { notifyListeners(); - if (AppState().currentAppType == AppType.customer) {} else {} + if (AppState().currentAppType == AppType.customer) { + } else {} + return true; + } + return false; + } + + // ========================General ================== + + List currentMessagesForGeneralChat = []; + + Future getUsersChatMessagesForGeneralChat({required BuildContext context, int? adID, int? adsChatBuyerId, String? userID, String? senderName, required bool isForBuyer}) async { + try { + Utils.showLoading(context); + currentMessagesForGeneralChat = await chatRepo.getUsersChatMessagesForGeneralChat(userID: userID, adID: adID, isForBuyer: isForBuyer, adsChatBuyerId: adsChatBuyerId); + Utils.hideLoading(context); + + List unreadMessageIds = []; + + for (var msg in currentMessagesForGeneralChat) { + msg.senderName = senderName ?? ""; + if (!msg.isRead! && !msg.isMyMessage!) { + unreadMessageIds.add(msg.id!); + } + } + + if (unreadMessageIds.isNotEmpty) { + await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.general); + } + notifyListeners(); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + Utils.hideLoading(context); + } + } + + Future onNewMessageReceivedForGeneral({required List messages}) async { + for (var msg in messages) { + currentMessagesForAds.add(msg); + } + scrollChatDown(); + notifyListeners(); + } + + void subscribeToReceiveGeneralMessages(BuildContext context) { + hubConnection!.on(SignalrConsts.receiveMessageGeneral, (List? arguments) { + if (arguments == null || arguments.isEmpty) return; + List chat = []; + for (var message in arguments) { + final chatMessage = ChatMessageModel.fromJson(message as Map); + chat.add(chatMessage); + } + onNewMessageReceivedForGeneral(messages: chat); + logger.i(arguments); + // Utils.showToast(arguments.toString()); + }); + } + + Future onTextMessageSendForGeneralChat({ + required String receiverId, + required ChatMessageTypeEnum chatMessageType, + required String message, + required int adId, + required BuildContext context, + }) async { + if (message.isEmpty) return false; + + if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { + logger.i("Hub Not Connected!!"); + await buildHubConnection(context); + } + + if (hubConnection!.state == HubConnectionState.connected) { + final userId = AppState().getUser.data!.userInfo!.userId.toString(); + final name = AppState().getUser.data!.userInfo!.firstName.toString(); + final obj = { + "ReceiverUserID": receiverId, + "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), + "ChatText": message, + }; + logger.i("$obj"); + + hubConnection!.invoke( + SignalrConsts.sendMessageGeneral, + args: [obj], + ).catchError((e) { + logger.i("error in invoking SendMessage: ${e.toString()}"); + Utils.showToast(e.toString()); + return false; + }); + + ChatMessageModel chatMessageModel = ChatMessageModel( + messageType: chatMessageType.getIdFromChatMessageTypeEnum(), + chatText: message, + isMyMessage: true, + senderName: name, + senderUserID: userId, + chatMessageTypeEnum: ChatMessageTypeEnum.freeText, + receiverUserID: receiverId, + ); + + currentMessagesForGeneralChat.add(chatMessageModel); + notifyListeners(); + + if (AppState().currentAppType == AppType.customer) { + } else {} return true; } return false; diff --git a/lib/view_models/service_view_model.dart b/lib/view_models/service_view_model.dart index eef76a4..ce58286 100644 --- a/lib/view_models/service_view_model.dart +++ b/lib/view_models/service_view_model.dart @@ -94,6 +94,14 @@ class ServiceVM extends BaseVM { Future getServiceProviderDocument(int providerId) async { setState(ViewState.busy); document = await branchRepo.getServiceProviderDocument(providerId); + if (document != null && document!.data != null && document!.data!.isNotEmpty) { + for (var doc in document!.data!) { + log("doc: ${doc.status}"); + if (doc.status != 3) { + updateIsAllDocsApproved(false); + } + } + } setState(ViewState.idle); } @@ -202,13 +210,20 @@ class ServiceVM extends BaseVM { // return null; // } + bool isAllDocsApproved = false; + + updateIsAllDocsApproved(var value) { + isAllDocsApproved = value; + notifyListeners(); + } + Future pickPdfReceiptFile(BuildContext context, int documentID, int index) async { List imageModels = []; List? files = await commonServices.pickMultipleFiles( context, allowMultiple: false, ); - if (files == null) return null; + if (files == null) return; for (var element in files) { imageModels.add(ImageModel( filePath: element.path, @@ -218,8 +233,8 @@ class ServiceVM extends BaseVM { documentID == 1 ? commerceCertificates.addAll(imageModels) : documentID == 2 - ? commercialCertificates.addAll(imageModels) - : vatCertificates.addAll(imageModels); + ? commercialCertificates.addAll(imageModels) + : vatCertificates.addAll(imageModels); document!.data![index].document = Utils.convertFileToBase64(files.first); document!.data![index].fileExt = Utils.checkFileExt(files.first.path); document!.data![index].documentUrl = files.first.path; @@ -237,18 +252,6 @@ class ServiceVM extends BaseVM { notifyListeners(); } - Future removeNetworkImag(String filePath) async { - log("removeNetworkImag with: $filePath"); - - int index = document!.data!.indexWhere((element) => element.documentUrl == filePath); - if (index == -1) { - return; - } - document!.data![index].documentUrl = null; - document!.data![index].isLocalFile = true; - notifyListeners(); - } - Future commercialRemove(String filePath) async { log("commercialRemove with: $filePath"); @@ -271,6 +274,16 @@ class ServiceVM extends BaseVM { notifyListeners(); } + Future removeNetworkImage(String filePath) async { + int index = document!.data!.indexWhere((element) => element.documentUrl == filePath); + if (index == -1) { + return; + } + document!.data![index].documentUrl = null; + document!.data![index].isLocalFile = true; + notifyListeners(); + } + Future updateDocument(List? data) async { return await branchRepo.serviceProviderDocumentsUpdate(data); } @@ -400,10 +413,10 @@ class ServiceVM extends BaseVM { DropValue( element.id ?? 0, ((element.categoryName!.isEmpty - ? "N/A" - : countryCode == "SA" - ? element.categoryNameN - : element.categoryName) ?? + ? "N/A" + : countryCode == "SA" + ? element.categoryNameN + : element.categoryName) ?? "N/A"), "", ), @@ -586,9 +599,7 @@ class ServiceVM extends BaseVM { File file = File(imageModel.filePath!); List imageBytes = await file.readAsBytes(); String image = base64Encode(imageBytes); - String fileName = file.path - .split('/') - .last; + String fileName = file.path.split('/').last; branchPostingImages = BranchPostingImages( imageName: fileName, imageStr: image, diff --git a/lib/view_models/setting_options_view_model.dart b/lib/view_models/setting_options_view_model.dart new file mode 100644 index 0000000..d48d294 --- /dev/null +++ b/lib/view_models/setting_options_view_model.dart @@ -0,0 +1,59 @@ +import 'package:mc_common_app/main.dart'; +import 'package:mc_common_app/models/setting_utils_models/app_info_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/contact_infos_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/faqs_model.dart'; +import 'package:mc_common_app/repositories/setting_options_repo.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/view_models/base_view_model.dart'; + +class SettingOptionsVM extends BaseVM { + final SettingOptionsRepo settingOptionsRepo; + + SettingOptionsVM({required this.settingOptionsRepo}); + + List faqsList = []; + + Future getAllFaqs() async { + setState(ViewState.busy); + try { + faqsList = await settingOptionsRepo.getAllFaqs(); + setState(ViewState.idle); + notifyListeners(); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + setState(ViewState.idle); + } + } + + List contactInfosList = []; + + Future getAllContactInfosList() async { + setState(ViewState.busy); + try { + contactInfosList = await settingOptionsRepo.getAllContactInfos(); + setState(ViewState.idle); + notifyListeners(); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + setState(ViewState.idle); + } + } + + List appInfoList = []; + + Future getAppInfoList() async { + setState(ViewState.busy); + try { + appInfoList = await settingOptionsRepo.getAppInfoList(); + setState(ViewState.idle); + notifyListeners(); + } catch (e) { + logger.i(e.toString()); + Utils.showToast(e.toString()); + setState(ViewState.idle); + } + } +} diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index 913e345..9216b4f 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -279,11 +279,11 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { builder: (BuildContext context) { return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) { return InfoBottomSheet( - title: LocaleKeys.setDateandTime.tr().toText(fontSize: 16, isBold: true, letterSpacing: -1.44, height: 1.2), + title: LocaleKeys.setDateandTime.tr().toText(fontSize: 16, height: 1.2), description: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 25.height, + 20.height, adVM.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) : Builder( @@ -345,7 +345,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { ], ), ], - 19.height, + 30.height, ], )); }); @@ -579,7 +579,34 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { title: LocaleKeys.markAsSold.tr(), isBold: false, onPressed: () { - adVM.markAdAsSold(context, adId: adDetailsModel.id!); + return actionConfirmationBottomSheet( + context: context, + title: LocaleKeys.markAsSold.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + subtitle: LocaleKeys.markAsSold.tr(), + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.yes.tr(), + fontSize: 15, + onPressed: () { + adVM.markAdAsSold(context, adId: adDetailsModel.id!); + }, + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + isFilled: false, + borderColor: MyColors.darkPrimaryColor, + title: LocaleKeys.no.tr(), + txtColor: MyColors.darkPrimaryColor, + fontSize: 15, + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ); }, ), ), @@ -1017,6 +1044,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { case AdPostStatus.rejected: return pendingForReviewAction(pendingText: LocaleKeys.rejectedFormAdmin.tr()); case AdPostStatus.cancelled: + return pendingForReviewAction(pendingText: LocaleKeys.cancelledByOwner.tr()); case AdPostStatus.pendingForPost: return pendingForReviewAction(pendingText: LocaleKeys.waitingAdminPost.tr()); diff --git a/lib/views/advertisement/components/picked_images_container_widget.dart b/lib/views/advertisement/components/picked_images_container_widget.dart index ff41080..1e134bc 100644 --- a/lib/views/advertisement/components/picked_images_container_widget.dart +++ b/lib/views/advertisement/components/picked_images_container_widget.dart @@ -11,11 +11,11 @@ class PickedFilesContainer extends StatelessWidget { final Function(String filePath)? onCrossPressedPrimary; final Function(int index, String filePath)? onCrossPressedSecondary; final int? index; + final Function() onAddFilePressed; final bool isReview; final bool isPdf; final bool isFromNetwork; final bool allowAdButton; - final Function() onAddFilePressed; const PickedFilesContainer({ Key? key, diff --git a/lib/views/appointments/review_appointment_view.dart b/lib/views/appointments/review_appointment_view.dart index 7082f4e..1ecfa41 100644 --- a/lib/views/appointments/review_appointment_view.dart +++ b/lib/views/appointments/review_appointment_view.dart @@ -218,7 +218,7 @@ class ReviewAppointment extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Divider(thickness: 0.7, height: 3), + const Divider(thickness: 0.7, height: 3), 8.height, if (appointmentsVM.amountToPayForAppointment > 0) ...[ Row( diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index f50967b..fb7eb9c 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -23,7 +23,6 @@ import 'package:mc_common_app/views/requests/request_bottomsheets.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; -import 'package:mc_common_app/widgets/row_with_arrow.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart' as lcl; @@ -51,6 +50,9 @@ class _ChatViewState extends State { chatViewArgumentsForAd = widget.chatViewArguments.chatViewArgumentsForAd; } else if (chatTypeEnum == ChatTypeEnum.requestOffer) { chatViewArgumentsForRequest = widget.chatViewArguments.chatViewArgumentsForRequest; + } else if (chatTypeEnum == ChatTypeEnum.general) { + // For General Chat + chatViewArgumentsForAd = widget.chatViewArguments.chatViewArgumentsForAd; } chatVM = context.read(); chatVM.isUserOnChatScreen = true; @@ -86,7 +88,15 @@ class _ChatViewState extends State { adId: chatViewArgumentsForAd!.adsID!, context: context, ); - } else if (chatTypeEnum == ChatTypeEnum.general) {} + } else if (chatTypeEnum == ChatTypeEnum.general) { + status = await chatVM.onTextMessageSendForGeneralChat( + receiverId: chatViewArgumentsForAd!.receiverUserID ?? "", + chatMessageType: ChatMessageTypeEnum.freeText, + message: chatVM.chatMessageText, + adId: chatViewArgumentsForAd!.adsID!, + context: context, + ); + } Utils.hideLoading(context); return status; @@ -125,7 +135,9 @@ class _ChatViewState extends State { ), body: Consumer2(builder: (BuildContext context, ChatVM chatVM, RequestsVM requestVM, Widget? child) { List chatMessages = []; - if (chatTypeEnum == ChatTypeEnum.ads) { + if (chatTypeEnum == ChatTypeEnum.general) { + chatMessages = chatVM.currentMessagesForGeneralChat; + } else if (chatTypeEnum == ChatTypeEnum.ads) { chatMessages = chatVM.currentMessagesForAds; } else if (chatTypeEnum == ChatTypeEnum.requestOffer) { if (AppState().currentAppType == AppType.customer && chatVM.serviceProviderOffersList.isNotEmpty) { @@ -252,7 +264,6 @@ class _ChatViewState extends State { flex: 1, child: const Icon(Icons.send_rounded, color: MyColors.darkPrimaryColor, size: 30).onPress( () async { - debugPrint("AppState().getUser.data.userInfo.userId: ${AppState().getUser.data!.userInfo!.userId}"); ChatMessageTypeEnum chatMessageTypeEnum = ChatMessageTypeEnum.freeText; if (chatVM.pickedImagesForMessage.isNotEmpty) { diff --git a/lib/views/chat/general_chats_list_page.dart b/lib/views/chat/general_chats_list_page.dart new file mode 100644 index 0000000..ab7eafe --- /dev/null +++ b/lib/views/chat/general_chats_list_page.dart @@ -0,0 +1,113 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/provider_offers_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/view_models/chat_view_model.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class GeneralChatListPage extends StatelessWidget { + final OfferListPageArguments generalChatListPageArguments; + + const GeneralChatListPage({super.key, required this.generalChatListPageArguments}); + + @override + Widget build(BuildContext context) { + final List serviceProviderOffers = generalChatListPageArguments.serviceProviderOffers; + return Scaffold( + appBar: CustomAppBar(title: LocaleKeys.offers.tr()), + body: serviceProviderOffers.isEmpty + ? Center(child: LocaleKeys.noOffersShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) + : ListView.separated( + itemCount: serviceProviderOffers.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + ServiceProvidersOffers offersModel = serviceProviderOffers[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.statusContainerChip(text: "Offer ${Utils.getNameByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!)}", chipColor: Utils.getChipColorByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (offersModel.name ?? "").toText(fontSize: 16, isBold: true), + Center( + child: "${offersModel.offerCount}".toText( + color: Colors.white, + isBold: true, + fontSize: 10, + ), + ).toContainer( + backgroundColor: MyColors.redColor, + borderRadius: 100, + paddingAll: 1, + width: 22, + height: 22, + ), + ], + ), + 8.height, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + "${offersModel.companyName}".toText(color: MyColors.lightTextColor, fontSize: 14), + if (offersModel.createdOn != null && offersModel.createdOn!.isNotEmpty) ...[ + " | ${DateTime.parse(offersModel.createdOn!).getTimeAgo()}".toText(color: MyColors.lightTextColor, fontSize: 14), + ], + // " | 1 hour ago".toText( + // color: MyColors.lightTextColor, + // fontSize: 12, + // ), + ], + ), + const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), + ], + ), + ], + ).onPress(() async { + ChatViewArgumentsForRequest chatViewArgumentsForRequest = ChatViewArgumentsForRequest( + chatTypeEnum: ChatTypeEnum.requestOffer, + receiverId: "${offersModel.providerUserId}", + senderId: AppState().getUser.data!.userInfo!.userId.toString(), + requestId: generalChatListPageArguments.requestId, + providerIndex: index, + requestIndex: -1, // This will be only send in case of provider + ); + + ChatViewArguments chatViewArguments = ChatViewArguments(chatTypeEnum: ChatTypeEnum.requestOffer, chatViewArgumentsForRequest: chatViewArgumentsForRequest); + + final chatVM = context.read(); + + await chatVM + .getRequestsChatMessagesForCustomer( + context: context, + providerId: offersModel.providerId ?? 0, + requestOfferId: 0, + requestId: generalChatListPageArguments.requestId ?? 0, + providerOfferIndex: index, + ) + .whenComplete( + () => navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments), + ); + }).toContainer(isShadowEnabled: true); + }, + separatorBuilder: (context, index) => 16.height, + ), + ); + } +} diff --git a/lib/views/common_fragments/ads_fragment.dart b/lib/views/common_fragments/ads_fragment.dart index 4125729..9b08eb8 100644 --- a/lib/views/common_fragments/ads_fragment.dart +++ b/lib/views/common_fragments/ads_fragment.dart @@ -125,8 +125,13 @@ class AdsFragment extends StatelessWidget { Expanded( child: RefreshIndicator( onRefresh: () async { - await adVM.getExploreAds(); - await adVM.getMyAds(); + if (adVM.isExploreAdsTapped) { + CreatedByRoleEnum createdByRoleEnum = adVM.exploreAdsFilterOptions.firstWhere((element) => element.isSelected).id.toCreatedByRoleEnum(); + adVM.applyFilterOnExploreAds(createdByRoleFilter: createdByRoleEnum); + } else { + AdPostStatus adPostStatusEnum = adVM.myAdsFilterOptions.firstWhere((element) => element.isSelected).id.toAdPostEnum(); + adVM.applyFilterOnMyAds(adPostStatusEnum: adPostStatusEnum); + } }, child: adVM.state == ViewState.busy ? const Center( diff --git a/lib/views/setting_options/provider_license_page.dart b/lib/views/setting_options/provider_license_page.dart index afad8bb..3718669 100644 --- a/lib/views/setting_options/provider_license_page.dart +++ b/lib/views/setting_options/provider_license_page.dart @@ -1,8 +1,5 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:developer'; -import 'dart:io'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; @@ -10,7 +7,6 @@ import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; -import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/provider_branches_models/profile/document.dart'; import 'package:mc_common_app/theme/colors.dart'; @@ -27,7 +23,6 @@ import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; -import 'package:sizer/sizer.dart'; class ProviderLicensePage extends StatefulWidget { const ProviderLicensePage({super.key}); @@ -40,35 +35,25 @@ class _ProviderLicensePageState extends State { late ServiceVM branchVM; bool showAttachment = false; String? attachedFile; - bool isAllApproved = true; @override void initState() { super.initState(); - scheduleMicrotask(() { - // branchVM = Provider.of(context, listen: false); - context.read().getServiceProviderDocument(AppState().getUser.data!.userInfo!.providerId ?? 0); + scheduleMicrotask(() async { + final serviceVm = context.read(); + await serviceVm.getServiceProviderDocument(AppState().getUser.data!.userInfo!.providerId ?? 0); }); } @override Widget build(BuildContext context) { - final serviceVm = context.read(); - if (serviceVm.document != null && serviceVm.document!.data != null && serviceVm.document!.data!.isNotEmpty) { - for (var doc in serviceVm.document!.data!) { - if (doc.status != 3) { - isAllApproved = false; - } - } - setState(() {}); - } return Scaffold( appBar: CustomAppBar( title: LocaleKeys.defineLicences.tr(), isRemoveBackButton: false, ), - body: Consumer(builder: (_, model, __) { - if (model.state == ViewState.busy) { + body: Consumer(builder: (_, serviceVM, __) { + if (serviceVM.state == ViewState.busy) { return const Center( child: CircularProgressIndicator(), ); @@ -77,9 +62,9 @@ class _ProviderLicensePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: SingleChildScrollView(child: buildContent(model)), + child: SingleChildScrollView(child: buildContent(serviceVM)), ), - if (!isAllApproved) ...[ + if (!serviceVM.isAllDocsApproved) ...[ Padding( padding: const EdgeInsets.all(12.0), child: ShowFillButton( @@ -88,14 +73,14 @@ class _ProviderLicensePageState extends State { onPressed: () async { bool status = false; if (AppState().getUser.data!.userInfo!.roleId == 5) { - if (validation(model)) { - status = await updateDocument(model); + if (validateDocuments(serviceVM)) { + status = await updateDocument(serviceVM); } else { Utils.showToast(LocaleKeys.allDocumentMandatoryDealershipProvider.tr()); return; } } else { - status = await updateDocument(model); + status = await updateDocument(serviceVM); } Future.delayed(const Duration(seconds: 1), () { if (status) { @@ -112,9 +97,10 @@ class _ProviderLicensePageState extends State { ); } - validation(ServiceVM model) { + validateDocuments(ServiceVM model) { bool valid = true; for (var element in model.document!.data!) { + log("documentUrl: ${element.documentUrl}"); if (element.documentUrl == null) { valid = false; } @@ -140,19 +126,19 @@ class _ProviderLicensePageState extends State { } } - Widget buildContent(ServiceVM model) { - return model.document!.data!.isEmpty + Widget buildContent(ServiceVM serviceVM) { + return serviceVM.document!.data!.isEmpty ? Text(LocaleKeys.somethingWrong.tr()) : ListView.separated( separatorBuilder: (context, index) { return 20.height; }, - itemCount: model.document!.data!.length, + itemCount: serviceVM.document!.data!.length, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, padding: const EdgeInsets.symmetric(horizontal: 20), itemBuilder: (context, index) { - DocumentData? document = model.document?.data![index]; + DocumentData? document = serviceVM.document?.data![index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -179,28 +165,29 @@ class _ProviderLicensePageState extends State { ), ], 10.height, - if (isNeedToShow(model: model, document: document)) ...[ + if (isNeedToShow(model: serviceVM, document: document)) ...[ PickedFilesContainer( - isReview: document.status != 4, - pickedFiles: isLocalOrNetworkFiles(model: model, document: document), + isReview: document.status == 4, + allowAdButton: false, + pickedFiles: isLocalOrNetworkFiles(model: serviceVM, document: document), onCrossPressedPrimary: chkIsLocalOrNetwork(document: document) - ? model.removeNetworkImag + ? serviceVM.removeNetworkImage : document.documentId == 1 - ? model.commerceRemove + ? serviceVM.commerceRemove : document.documentId == 2 - ? model.commercialRemove - : model.vatRemove, - isPdf: model.document!.data![index].fileExt == "pdf", + ? serviceVM.commercialRemove + : serviceVM.vatRemove, + isPdf: serviceVM.document!.data![index].fileExt == "pdf", isFromNetwork: !(document.isLocalFile ?? false), onAddFilePressed: () { - model.pickPdfReceiptFile(context, document.documentId!, index); + serviceVM.pickPdfReceiptFile(context, document.documentId!, index); }, ), buildCommentContainer(document: document), ] else ...[ 10.height, DottedRectContainer( - onTap: () => model.pickPdfReceiptFile(context, document.documentId!, index) ?? "", + onTap: () => serviceVM.pickPdfReceiptFile(context, document.documentId!, index) ?? "", text: LocaleKeys.attachImage.tr(), icon: MyAssets.attachmentIcon.buildSvg(), ), @@ -214,7 +201,7 @@ class _ProviderLicensePageState extends State { List isLocalOrNetworkFiles({required ServiceVM model, required DocumentData document}) { bool isNetworkImage = false; if (!document.isLocalFile!) { - isNetworkImage = document.documentUrl != null && document.documentUrl.isNotEmpty ? true : false; + isNetworkImage = document.documentUrl != null && document.documentUrl!.isNotEmpty ? true : false; } if (isNetworkImage) { return [ImageModel(id: document.id, isFromNetwork: isNetworkImage, filePath: document.documentUrl)]; @@ -229,7 +216,8 @@ class _ProviderLicensePageState extends State { bool isNeedToShow({required ServiceVM model, required DocumentData document}) { bool allow = false; - bool isNetworkImage = document.documentUrl != null && document.documentUrl.isNotEmpty ? true : false; + bool isNetworkImage = document.documentUrl != null && document.documentUrl!.isNotEmpty && !(document.isLocalFile ?? true); + log("document.documentUrl: ${document.documentUrl}"); if (isNetworkImage) { allow = true; } else { @@ -247,7 +235,7 @@ class _ProviderLicensePageState extends State { } dynamic checkOnCrossPress({required ServiceVM model, required DocumentData document}) async { - bool isNetworkImage = document.documentUrl != null && document.documentUrl.isNotEmpty ? true : false; + bool isNetworkImage = document.documentUrl != null && document.documentUrl!.isNotEmpty ? true : false; if (isNetworkImage) { return document.documentUrl; } else { @@ -266,7 +254,7 @@ class _ProviderLicensePageState extends State { bool chkIsLocalOrNetwork({required DocumentData document}) { bool isNetworkImage = false; if (!document.isLocalFile!) { - isNetworkImage = document.documentUrl != null && document.documentUrl.isNotEmpty ? true : false; + isNetworkImage = document.documentUrl != null && document.documentUrl!.isNotEmpty ? true : false; } return isNetworkImage; } @@ -274,7 +262,7 @@ class _ProviderLicensePageState extends State { Widget buildCommentContainer({required DocumentData document}) { String comment = ""; if (document.status == 4 && document.comment != null) { - comment = document.comment; + comment = document.comment ?? ""; } if (comment.isEmpty) { diff --git a/lib/views/setting_options/setting_option_help.dart b/lib/views/setting_options/setting_option_help.dart index 6ffb72f..38fc246 100644 --- a/lib/views/setting_options/setting_option_help.dart +++ b/lib/views/setting_options/setting_option_help.dart @@ -3,7 +3,9 @@ import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -28,24 +30,46 @@ class SettingOptionsHelp extends StatelessWidget { Column( children: [ CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.info_rounded, size: 20), + leadingWidget: const Icon( + Icons.info_rounded, + size: 20, + color: MyColors.greyColor, + ), titleText: LocaleKeys.faqs.tr(), needBorderBelow: true, - onTap: () {}, + onTap: () => navigateWithName(context, AppRoutes.settingOptionsFaqs), ), CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.mail, size: 20), + leadingWidget: const Icon( + Icons.mail, + size: 20, + color: MyColors.greyColor, + ), titleText: LocaleKeys.contactUs.tr(), needBorderBelow: true, - onTap: () => navigateWithName(context, AppRoutes.settingOptionsFaqs), + onTap: () => navigateWithName(context, AppRoutes.settingOptionsContactUs), ), CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.lock, size: 20), + leadingWidget: const Icon( + Icons.lock, + size: 20, + color: MyColors.greyColor, + ), titleText: LocaleKeys.termPrivacy.tr(), + needBorderBelow: true, onTap: () {}, ), + CustomSettingOptionsTile( + leadingWidget: const Icon( + Icons.info_rounded, + size: 20, + color: MyColors.greyColor, + ), + titleText: LocaleKeys.appInfo.tr(), + onTap: () => navigateWithName(context, AppRoutes.settingOptionsAppInfo), + ), ], - ).toContainer(width: double.infinity, isShadowEnabled: true, paddingAll: 10, margin: const EdgeInsets.fromLTRB(24, 24, 24, 0), borderRadius: 0), + ).toContainer(width: double.infinity, isShadowEnabled: true, paddingAll: 10, margin: const EdgeInsets.fromLTRB(21, 24, 21, 0), borderRadius: 0), ], ), ), @@ -54,38 +78,3 @@ class SettingOptionsHelp extends StatelessWidget { ); } } - -class CustomSettingOptionsTile extends StatelessWidget { - final Widget leadingWidget; - final String titleText; - final bool needBorderBelow; - final bool isForLanguage; - final Function() onTap; - - const CustomSettingOptionsTile({super.key, required this.leadingWidget, required this.onTap, required this.titleText, this.needBorderBelow = false, this.isForLanguage = false}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - leadingWidget, - 12.width, - titleText.toText(fontSize: 16), - ], - ), - isForLanguage ? const Icon(Icons.language, size: 18) : const Icon(Icons.arrow_forward, size: 18) - ], - ).onPress(onTap), - 5.height, - if (needBorderBelow) ...[ - const Divider(thickness: 1), - ], - ], - ); - } -} diff --git a/lib/views/setting_options/setting_options_app_info.dart b/lib/views/setting_options/setting_options_app_info.dart new file mode 100644 index 0000000..998a514 --- /dev/null +++ b/lib/views/setting_options/setting_options_app_info.dart @@ -0,0 +1,117 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/setting_utils_models/app_info_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/contact_infos_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/faqs_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/view_models/ad_view_model.dart'; +import 'package:mc_common_app/view_models/setting_options_view_model.dart'; +import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:provider/provider.dart'; + +class SettingOptionsAppInfo extends StatefulWidget { + const SettingOptionsAppInfo({super.key}); + + @override + State createState() => _SettingOptionsAppInfoState(); +} + +class _SettingOptionsAppInfoState extends State { + late SettingOptionsVM settingsOptionsVM; + + @override + void initState() { + settingsOptionsVM = context.read(); + scheduleMicrotask(() async { + await settingsOptionsVM.getAppInfoList(); + }); + super.initState(); + } + + Widget showData(String title, String value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText( + fontSize: 13, + color: MyColors.darkTextColor, + ), + if (title.isNotEmpty) 5.width, + Flexible( + child: value.toText( + fontSize: 13, + color: MyColors.lightTextColor, + ), + ) + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: LocaleKeys.appInfo.tr(), + isRemoveBackButton: false, + isDrawerEnabled: false, + onBackButtonTapped: () => Navigator.pop(context), + ), + body: Consumer( + builder: (BuildContext context, SettingOptionsVM settingsOptionsVM, Widget? child) { + return Container( + color: MyColors.backgroundColor, + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + 16.height, + Expanded( + child: RefreshIndicator( + onRefresh: () async => await settingsOptionsVM.getAllFaqs(), + child: (settingsOptionsVM.state == ViewState.busy) + ? const Center(child: CircularProgressIndicator()) + : settingsOptionsVM.appInfoList.isEmpty + ? Padding( + padding: const EdgeInsets.all(21), + child: Center(child: LocaleKeys.somethingWrong.tr().toText(textAlign: TextAlign.center, fontSize: 16, color: MyColors.lightTextColor)), + ) + : ListView.separated( + itemCount: settingsOptionsVM.appInfoList.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + AppInfoModel appInfoModel = settingsOptionsVM.appInfoList[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (appInfoModel.header ?? "").toString().toText(fontSize: 16), + 5.height, + (appInfoModel.content ?? "").toString().toText(fontSize: 14, color: MyColors.lightTextColor), + if (appInfoModel.appInfoImages != null && appInfoModel.appInfoImages!.isNotEmpty) ...[ + PickedFilesContainer( + pickedFiles: appInfoModel.appInfoImages ?? [], + isReview: true, + onAddFilePressed: () {}, + ), + ] + ], + ).toContainer(isShadowEnabled: true); + }, + separatorBuilder: (context, index) => 16.height, + ), + )), + ], + ), + ); + }, + )); + } +} diff --git a/lib/views/setting_options/setting_options_contact_us.dart b/lib/views/setting_options/setting_options_contact_us.dart new file mode 100644 index 0000000..05bf650 --- /dev/null +++ b/lib/views/setting_options/setting_options_contact_us.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/setting_utils_models/contact_infos_model.dart'; +import 'package:mc_common_app/models/setting_utils_models/faqs_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/view_models/ad_view_model.dart'; +import 'package:mc_common_app/view_models/setting_options_view_model.dart'; +import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:provider/provider.dart'; + +class SettingOptionsContactUs extends StatefulWidget { + const SettingOptionsContactUs({super.key}); + + @override + State createState() => _SettingOptionsContactUsState(); +} + +class _SettingOptionsContactUsState extends State { + late SettingOptionsVM settingsOptionsVM; + + @override + void initState() { + settingsOptionsVM = context.read(); + scheduleMicrotask(() async { + await settingsOptionsVM.getAllContactInfosList(); + }); + super.initState(); + } + + Widget showData(String title, String value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText( + fontSize: 13, + color: MyColors.darkTextColor, + ), + if (title.isNotEmpty) 5.width, + Flexible( + child: value.toText( + fontSize: 13, + color: MyColors.lightTextColor, + ), + ) + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: LocaleKeys.contactUs.tr(), + isRemoveBackButton: false, + isDrawerEnabled: false, + onBackButtonTapped: () => Navigator.pop(context), + ), + body: Consumer( + builder: (BuildContext context, SettingOptionsVM settingsOptionsVM, Widget? child) { + return Container( + color: MyColors.backgroundColor, + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + 16.height, + Expanded( + child: RefreshIndicator( + onRefresh: () async => await settingsOptionsVM.getAllFaqs(), + child: (settingsOptionsVM.state == ViewState.busy) + ? const Center(child: CircularProgressIndicator()) + : settingsOptionsVM.contactInfosList.isEmpty + ? Padding( + padding: const EdgeInsets.all(21), + child: Center(child: LocaleKeys.somethingWrong.tr().toText(textAlign: TextAlign.center, fontSize: 16, color: MyColors.lightTextColor)), + ) + : ListView.separated( + itemCount: settingsOptionsVM.contactInfosList.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + ContactInfoModel contactInfoModel = settingsOptionsVM.contactInfosList[index]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + contactInfoModel.companyName.toString().toText(fontSize: 16), + 5.height, + LocaleKeys.openMapLocation.tr().toText(isUnderLine: true, color: MyColors.darkPrimaryColor, fontSize: 10).onPress(() {}), + showData("${LocaleKeys.phoneNumber.tr()}:", "${contactInfoModel.phoneNo}"), + showData("${LocaleKeys.email.tr()}:", "${contactInfoModel.email}"), + if (contactInfoModel.address != null && contactInfoModel.address!.isNotEmpty) ...[ + showData("${LocaleKeys.address.tr()}:", ''), + "${contactInfoModel.address}".toText(height: 1.5, fontSize: 13, color: MyColors.lightTextColor), + ], + if (contactInfoModel.contactInfoImages != null && contactInfoModel.contactInfoImages!.isNotEmpty) ...[ + PickedFilesContainer( + pickedFiles: contactInfoModel.contactInfoImages ?? [], + isReview: true, + onAddFilePressed: () {}, + ), + ] + ], + ).toContainer(isShadowEnabled: true); + }, + separatorBuilder: (context, index) => 16.height, + ), + )), + ], + ), + ); + }, + )); + } +} diff --git a/lib/views/setting_options/setting_options_faqs.dart b/lib/views/setting_options/setting_options_faqs.dart index fb3abde..d6e7bfa 100644 --- a/lib/views/setting_options/setting_options_faqs.dart +++ b/lib/views/setting_options/setting_options_faqs.dart @@ -1,61 +1,104 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; -import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart'; +import 'package:mc_common_app/models/setting_utils_models/faqs_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/view_models/setting_options_view_model.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:provider/provider.dart'; -class SettingOptionsFAQs extends StatelessWidget { +class SettingOptionsFAQs extends StatefulWidget { const SettingOptionsFAQs({super.key}); + @override + State createState() => _SettingOptionsFAQsState(); +} + +class _SettingOptionsFAQsState extends State { + late SettingOptionsVM settingsOptionsVM; + + @override + void initState() { + settingsOptionsVM = context.read(); + scheduleMicrotask(() async { + await settingsOptionsVM.getAllFaqs(); + }); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: CustomAppBar( - title: LocaleKeys.more.tr(), - isRemoveBackButton: false, - isDrawerEnabled: false, - onBackButtonTapped: () => Navigator.pop(context), - ), - body: Column( - children: [ - Expanded( - child: ListView( - children: [ - Column( - children: [ - CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.info, size: 20), - titleText: LocaleKeys.faqs.tr(), - needBorderBelow: true, - onTap: () {}), - CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.mail, size: 20), - titleText: LocaleKeys.contactUs.tr(), - needBorderBelow: true, - onTap: () {}), - CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.settings, size: 20), - titleText: LocaleKeys.termPrivacy.tr(), - onTap: () {}), - ], - ).toContainer( - width: double.infinity, - isShadowEnabled: true, - paddingAll: 10, - margin: const EdgeInsets.fromLTRB(24, 24, 24, 0), - borderRadius: 0), - ], - ), - ), - Row( - children: [ - "Version: 1.0.0".toText(fontSize: 14), - ], - ).paddingAll(21), - ], - ), - ); + appBar: CustomAppBar( + title: LocaleKeys.faqs.tr(), + isRemoveBackButton: false, + isDrawerEnabled: false, + onBackButtonTapped: () => Navigator.pop(context), + ), + body: Consumer( + builder: (BuildContext context, SettingOptionsVM settingsOptionsVM, Widget? child) { + return Container( + color: MyColors.backgroundColor, + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + 16.height, + Expanded( + child: RefreshIndicator( + onRefresh: () async => await settingsOptionsVM.getAllFaqs(), + child: (settingsOptionsVM.state == ViewState.busy) + ? const Center(child: CircularProgressIndicator()) + : settingsOptionsVM.faqsList.isEmpty + ? Padding( + padding: const EdgeInsets.all(21), + child: Center(child: LocaleKeys.noFAQsToShow.tr().toText(textAlign: TextAlign.center, fontSize: 16, color: MyColors.lightTextColor)), + ) + : ListView.separated( + itemCount: settingsOptionsVM.faqsList.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + FAQsModel faqModel = settingsOptionsVM.faqsList[index]; + return ExpansionTile( + tilePadding: EdgeInsets.zero, + shape: const Border(), + title: (faqModel.question ?? "").toText(fontSize: 16).paddingOnly(left: 8, right: 8), + onExpansionChanged: (value) { + setState(() { + settingsOptionsVM.faqsList[index].isCollapsed = value; + }); + }, + backgroundColor: Colors.transparent, + collapsedBackgroundColor: Colors.transparent, + initiallyExpanded: settingsOptionsVM.faqsList[index].isCollapsed ?? false, + trailing: (settingsOptionsVM.faqsList[index].isCollapsed ?? false) ? const Icon(Icons.keyboard_arrow_up) : const Icon(Icons.keyboard_arrow_down), + subtitle: ("Channel: ${faqModel.channel.toString()}").toText(color: MyColors.lightTextColor).paddingOnly(left: 8, right: 8), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 5.height, + (faqModel.answer ?? "").toText(color: MyColors.lightTextColor, fontSize: 14, fontWeight: MyFonts.Medium), + 10.height, + ], + ).paddingOnly(left: 8, right: 8) + ], + ).toContainer(isShadowEnabled: true); + }, + separatorBuilder: (context, index) => 12.height, + ), + )), + ], + ), + ); + }, + )); } } diff --git a/lib/views/setting_options/setting_options_invite_friends.dart b/lib/views/setting_options/setting_options_invite_friends.dart index 2d48e3f..d09c9ab 100644 --- a/lib/views/setting_options/setting_options_invite_friends.dart +++ b/lib/views/setting_options/setting_options_invite_friends.dart @@ -1,21 +1,76 @@ import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; -import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:easy_localization/easy_localization.dart'; class SettingOptionsInviteFriends extends StatelessWidget { const SettingOptionsInviteFriends({super.key}); + buildInviteFriendsBottomSheet(BuildContext context) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + builder: (BuildContext context) { + return InfoBottomSheet( + description: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 20.height, + CustomSettingOptionsTile( + leadingWidget: MyAssets.whatsAppIcon.buildSvg( + height: 20, + width: 20, + color: MyColors.greyColor, + ), + titleText: LocaleKeys.inviteFriendsByWhatsApp.tr(), + needBorderBelow: true, + showTrailingArrow: false, + onTap: () {}, + ), + CustomSettingOptionsTile( + leadingWidget: const Icon( + Icons.sms_outlined, + size: 20, + color: MyColors.greyColor, + ), + titleText: LocaleKeys.inviteFriendsBySMS.tr(), + needBorderBelow: true, + showTrailingArrow: false, + onTap: () {}, + ), + CustomSettingOptionsTile( + leadingWidget: const Icon( + Icons.email_outlined, + size: 20, + color: MyColors.greyColor, + ), + titleText: LocaleKeys.inviteFriendsByEmail.tr(), + needBorderBelow: false, + showTrailingArrow: false, + onTap: () {}, + ), + 30.height, + ], + ), + title: const SizedBox()); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar( - title: LocaleKeys.more.tr(), + title: LocaleKeys.settings.tr(), isRemoveBackButton: false, isDrawerEnabled: false, onBackButtonTapped: () => Navigator.pop(context), @@ -28,31 +83,34 @@ class SettingOptionsInviteFriends extends StatelessWidget { Column( children: [ CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.person, size: 20), + leadingWidget: const Icon(Icons.group, size: 20), titleText: LocaleKeys.inviteFriends.tr(), needBorderBelow: true, - onTap: () {}, + onTap: () { + return buildInviteFriendsBottomSheet(context); + }, ), CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.help, size: 20), + leadingWidget: const Icon(Icons.question_mark_outlined, size: 20), titleText: LocaleKeys.help.tr(), needBorderBelow: true, - onTap: () => navigateWithName( - context, AppRoutes.settingOptionsFaqs), + onTap: () => navigateWithName(context, AppRoutes.settingOptionsHelp), ), CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.person, size: 20), - titleText: LocaleKeys.account.tr(), - onTap: () => - navigateWithName(context, AppRoutes.profileView), - ), + leadingWidget: const Icon(Icons.person, size: 20), + titleText: LocaleKeys.account.tr(), + needBorderBelow: false, + onTap: () { + navigateWithName(context, AppRoutes.profileView); + }), ], ).toContainer( - width: double.infinity, - isShadowEnabled: true, - paddingAll: 10, - margin: const EdgeInsets.fromLTRB(24, 24, 24, 0), - borderRadius: 0), + width: double.infinity, + isShadowEnabled: true, + paddingAll: 10, + margin: const EdgeInsets.fromLTRB(21, 21, 21, 0), + borderRadius: 0, + ), ], ), ), @@ -61,46 +119,3 @@ class SettingOptionsInviteFriends extends StatelessWidget { ); } } - -class CustomSettingOptionsTile extends StatelessWidget { - final Widget leadingWidget; - final String titleText; - final bool needBorderBelow; - final bool isForLanguage; - final Function() onTap; - - const CustomSettingOptionsTile( - {super.key, - required this.leadingWidget, - required this.onTap, - required this.titleText, - this.needBorderBelow = false, - this.isForLanguage = false}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - leadingWidget, - 12.width, - titleText.toText(fontSize: 16), - ], - ), - isForLanguage - ? const Icon(Icons.language, size: 18) - : const Icon(Icons.arrow_forward, size: 18) - ], - ).onPress(onTap), - 5.height, - if (needBorderBelow) ...[ - const Divider(thickness: 1), - ], - ], - ); - } -} diff --git a/lib/views/setting_options/setting_options_language.dart b/lib/views/setting_options/setting_options_more.dart similarity index 88% rename from lib/views/setting_options/setting_options_language.dart rename to lib/views/setting_options/setting_options_more.dart index ea3da5a..39aa21b 100644 --- a/lib/views/setting_options/setting_options_language.dart +++ b/lib/views/setting_options/setting_options_more.dart @@ -17,14 +17,14 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -class SettingOptionsLanguage extends StatefulWidget { - const SettingOptionsLanguage({super.key}); +class SettingOptionsMore extends StatefulWidget { + const SettingOptionsMore({super.key}); @override - State createState() => _SettingOptionsLanguageState(); + State createState() => _SettingOptionsMoreState(); } -class _SettingOptionsLanguageState extends State { +class _SettingOptionsMoreState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -65,25 +65,10 @@ class _SettingOptionsLanguageState extends State { CustomSettingOptionsTile( leadingWidget: const Icon(Icons.settings, size: 20), titleText: LocaleKeys.settings.tr(), - needBorderBelow: true, - onTap: () {}, - //navigateWithName(context, AppRoutes.settingOptionsInviteFriends), + needBorderBelow: false, + onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends), ), - CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.question_mark_outlined, size: 20), - titleText: LocaleKeys.help.tr(), - needBorderBelow: true, - onTap: () => navigateWithName(context, AppRoutes.settingOptionsHelp), - ), - CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.person, size: 20), - titleText: LocaleKeys.account.tr(), - needBorderBelow: false, - onTap: () { - navigateWithName(context, AppRoutes.profileView); - // context.read().onNavbarTapped(4); - // Navigator.pop(context); - }), + ], ).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0), 10.height, diff --git a/lib/views/setting_options/widgets/custom_setting_options_tile.dart b/lib/views/setting_options/widgets/custom_setting_options_tile.dart index 748a7d4..2ecd66a 100644 --- a/lib/views/setting_options/widgets/custom_setting_options_tile.dart +++ b/lib/views/setting_options/widgets/custom_setting_options_tile.dart @@ -6,13 +6,13 @@ import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; -import 'package:sizer/sizer.dart'; class CustomSettingOptionsTile extends StatelessWidget { final Widget leadingWidget; final String titleText; final bool needBorderBelow; final bool isForLanguage; + final bool showTrailingArrow; final Function() onTap; final String? subTitle; @@ -23,6 +23,7 @@ class CustomSettingOptionsTile extends StatelessWidget { required this.titleText, this.needBorderBelow = false, this.isForLanguage = false, + this.showTrailingArrow = true, this.subTitle, }); @@ -62,7 +63,9 @@ class CustomSettingOptionsTile extends StatelessWidget { // ) // - : const Icon(Icons.arrow_forward, size: 18) + : showTrailingArrow + ? const Icon(Icons.arrow_forward, size: 18) + : const SizedBox() ], ).onPress(onTap), subTitle != null ? 3.height : 5.height, @@ -97,9 +100,7 @@ class CustomProfileOptionsTile extends StatelessWidget { subtitleText.toText(fontSize: 12, color: MyColors.darkTextColor.withOpacity(0.7)), ], ), - if (needEditButton) ...[ - MyAssets.icEdit.buildSvg() - ], + if (needEditButton) ...[MyAssets.icEdit.buildSvg()], ], ).onPress(onTap), 5.height, diff --git a/lib/widgets/extensions/extensions_widget.dart b/lib/widgets/extensions/extensions_widget.dart index c966632..4bdc8b5 100644 --- a/lib/widgets/extensions/extensions_widget.dart +++ b/lib/widgets/extensions/extensions_widget.dart @@ -375,7 +375,12 @@ extension BuildSVG on String? { return Image.network( this!, errorBuilder: (BuildContext context, Object obj, StackTrace? s) { - return SizedBox(height: height, width: width, child: const Icon(Icons.signal_wifi_connected_no_internet_4_outlined)); + return Container( + height: height, + width: width, + decoration: BoxDecoration(border: Border.all(color: MyColors.roundedCrossBgColor)), + child: const Icon(Icons.image_not_supported_outlined, color: MyColors.roundedCrossBgColor), + ); }, loadingBuilder: (BuildContext context, Widget? child, ImageChunkEvent? imageChunk) { if (imageChunk == null) {