diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index c2c27015..c82f70fb 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -107,7 +107,7 @@ Future init() async { sl.registerFactory(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl())); sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl())); sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); - sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl())); + sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl())); sl.registerFactory(() => profile_plans.PlansCubit(plansRepository: sl())); sl.registerFactory(() => DietsCubit(feedRepository: sl(), dietsRepository: sl())); sl.registerFactory(() => CommentsCubit(feedRepository: sl())); diff --git a/lib/features/consultant/become_consultant/models/consultation_request_model.dart b/lib/features/consultant/become_consultant/models/consultation_request_model.dart new file mode 100644 index 00000000..aa7377b1 --- /dev/null +++ b/lib/features/consultant/become_consultant/models/consultation_request_model.dart @@ -0,0 +1,26 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultation_request_model.freezed.dart'; + +part 'consultation_request_model.g.dart'; + +@freezed +class ConsultationRequestModel with _$ConsultationRequestModel { + const factory ConsultationRequestModel({ + @JsonKey(name: 'rating_place') required int ratingPlace, + @JsonKey(name: 'education_resource') required EducationModel educationModel, + @JsonKey(name: 'cert_photos') required List photos, + }) = _ConsultationRequestModel; + + factory ConsultationRequestModel.fromJson(Map json) => _$ConsultationRequestModelFromJson(json); +} + +@freezed +class CertPhotosModel with _$CertPhotosModel { + const factory CertPhotosModel({ + required String url, + }) = _CertPhotosModel; + + factory CertPhotosModel.fromJson(Map json) => _$CertPhotosModelFromJson(json); +} diff --git a/lib/features/consultant/become_consultant/repositories/consultant_repository.dart b/lib/features/consultant/become_consultant/repositories/consultant_repository.dart index 3abdc799..6af88cb5 100644 --- a/lib/features/consultant/become_consultant/repositories/consultant_repository.dart +++ b/lib/features/consultant/become_consultant/repositories/consultant_repository.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart'; import 'package:http_parser/http_parser.dart'; import 'package:dio/dio.dart'; @@ -33,7 +34,9 @@ class ConsultantRepository { final fileBytes = await file.readAsBytes(); final multipartFile = MultipartFile.fromBytes( fileBytes, - filename: file.path.split('/').last, + filename: file.path + .split('/') + .last, contentType: MediaType('application', 'octet-stream'), ); multipartFiles.add(multipartFile); @@ -46,4 +49,12 @@ class ConsultantRepository { rethrow; } } + + Future getConsultantRequestStatus(String slug) async { + try { + return await consultantService.getConsultantRequestStatus(slug: slug); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/consultant/become_consultant/services/consultant_service.dart b/lib/features/consultant/become_consultant/services/consultant_service.dart index 4479122f..cc284993 100644 --- a/lib/features/consultant/become_consultant/services/consultant_service.dart +++ b/lib/features/consultant/become_consultant/services/consultant_service.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; import 'package:retrofit/retrofit.dart'; part 'consultant_service.g.dart'; @@ -18,4 +19,9 @@ abstract class ConsultantService { Future> uploadPhotos({ @Part(name: 'photos[]', contentType: 'multipart/form-data') List? photos, }); + + @GET('/api/consultant_request/{slug}/status') + Future getConsultantRequestStatus({ + @Path() required String slug, + }); } diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index 71fb1729..cdbab793 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; @@ -10,20 +11,24 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class ProfileCubit extends Cubit { final ProfileRepository profileRepository; final ChatRepository chatRepository; + final ConsultantRepository consultantRepository; String _currentPeriod = '1'; ProfileCubit({ required this.profileRepository, required this.chatRepository, + required this.consultantRepository, }) : super(const ProfileState.loading()); Future getProfileBySlug(String slug) async { try { + final profile = await profileRepository.getProfileBySlug(slug); emit( ProfileState.loaded( - profile: await profileRepository.getProfileBySlug(slug), + profile: profile, profileDetails: await profileRepository.getProfileDetailsBySlug(slug), blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug), + consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null, ), ); } catch (e) { @@ -44,6 +49,7 @@ class ProfileCubit extends Cubit { blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug), weightPoints: await profileRepository.getWeight(period: period), circumferencePoints: await profileRepository.getCircumferences(period: period), + consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(profile.slug) : null, ), ); } catch (e, stackTrace) { diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart index ec7fd13d..6e8154fc 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; @@ -18,6 +19,7 @@ abstract class ProfileState with _$ProfileState { required List blogPosts, List? weightPoints, List? circumferencePoints, + ConsultationRequestModel? consultantInfo, }) = ProfileStateLoaded; const factory ProfileState.error({ diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index df96f608..699aa021 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -93,7 +93,9 @@ class _ProfileScreenState extends State { } else if (state is ProfileStateError) { return DataErrorWidget( onReload: () async { - widget.author == null || widget.author!.authorIsMe ? await profileCubit.getMyProfile() : await profileCubit.getProfileBySlug(widget.author!.authorSlug); + widget.author == null || widget.author!.authorIsMe + ? await profileCubit.getMyProfile() + : await profileCubit.getProfileBySlug(widget.author!.authorSlug); }, ); } else { @@ -144,25 +146,24 @@ class _ProfileScreenState extends State { ), ), const SizedBox(width: 12), - if (state.profile.ratingPlace != null) - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: const Color(0xFFF6FAFF), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - '${state.profile.ratingPlace!}-й в рейтинге', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: appTheme.blueA400, - ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF6FAFF), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${state.consultantInfo!.ratingPlace}-й в рейтинге', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: appTheme.blueA400, ), ), + ), ], ), - if (state.profile.educationResource?.hasMedicalEducation == 1) + if (state.consultantInfo!.educationModel.hasMedicalEducation == 1) const Row( children: [ SizedBox(width: 16), @@ -221,6 +222,61 @@ class _ProfileScreenState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ _header(state), + if (state.profile.roles.contains('consultant')) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const SizedBox(width: 16), + const Text( + 'Консультант', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color( + 0xFF323F4B, + ), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF6FAFF), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${state.consultantInfo!.ratingPlace}-й в рейтинге', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: appTheme.blueA400, + ), + ), + ), + ], + ), + if (state.consultantInfo!.educationModel.hasMedicalEducation == 1) + const Row( + children: [ + SizedBox(width: 16), + Icon( + Icons.done_rounded, + color: Colors.green, + ), + Text( + 'Медицинское образование', + style: TextStyle( + color: Color(0xFF323F4B), + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state), ], ), @@ -371,7 +427,7 @@ class _ProfileScreenState extends State { } _diplomasAndCertificates(ProfileStateLoaded state) { - if (!state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) { + if (!state.profile.roles.contains('consultant') || state.consultantInfo!.photos.isEmpty) { return const SizedBox.shrink(); } return Padding( @@ -416,8 +472,18 @@ class _ProfileScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ - if (state.profile.diplomasAndCertificates != null) - ...state.profile.diplomasAndCertificates!.map((d) => _diplomaImage('$baseUrl/storage/${d.path}')), + if (state.consultantInfo!.photos.isNotEmpty) + ...List.generate( + state.consultantInfo!.photos.length * 2 - 1, + (index) { + if (index.isEven) { + final photo = state.consultantInfo!.photos[index ~/ 2]; + return _diplomaImage(photo.url); + } else { + return const SizedBox(width: 8); + } + }, + ), ], ), ), @@ -456,8 +522,8 @@ class _ProfileScreenState extends State { _education(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant') || - state.profile.educationResource?.completedCourses == null || - (state.profile.educationResource?.completedCourses?.isEmpty ?? false)) { + state.consultantInfo!.educationModel.completedCourses == null || + (state.consultantInfo!.educationModel.completedCourses?.isEmpty ?? false)) { return const SizedBox.shrink(); } return Padding( @@ -495,11 +561,17 @@ class _ProfileScreenState extends State { color: Colors.white, borderRadius: BorderRadius.circular(12), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.only(left: 16, right: 16, top: 20), child: Column( children: [ - if (state.profile.educationResource?.completedCourses != null) - ...state.profile.educationResource!.completedCourses!.map((c) => _educationBlock(c.values.first.title, c.values.first.date)), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _educationBlock(state.consultantInfo!.educationModel.completedCourses![index].values.first.title, + state.consultantInfo!.educationModel.completedCourses![index].values.first.date), + separatorBuilder: (context, index) => const SizedBox(height: 8), + itemCount: state.consultantInfo!.educationModel.completedCourses!.length, + ), ], ), ), @@ -564,39 +636,39 @@ class _ProfileScreenState extends State { () { launchUrl( Uri.parse( - state.profile.educationResource?.vkLink ?? '', + state.consultantInfo!.educationModel.vkLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/vk.svg', - state.profile.educationResource?.vkLink == null ? true : false, + state.consultantInfo!.educationModel.vkLink!.isEmpty ? true : false, ), const SizedBox(width: 12), _socialLinkButton( () { launchUrl( Uri.parse( - state.profile.educationResource?.vkLink ?? '', + state.consultantInfo!.educationModel.tgLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/telegram.svg', - state.profile.educationResource?.vkLink == null ? true : false, + state.consultantInfo!.educationModel.tgLink!.isEmpty ? true : false, ), const SizedBox(width: 12), _socialLinkButton( () { launchUrl( Uri.parse( - state.profile.educationResource?.personalSiteLink ?? '', + state.consultantInfo!.educationModel.personalSiteLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/web.svg', - state.profile.educationResource?.personalSiteLink == null ? true : false, + state.consultantInfo!.educationModel.personalSiteLink!.isEmpty ? true : false, ), ], ), @@ -620,7 +692,7 @@ class _ProfileScreenState extends State { child: SvgPicture.asset( icon, width: 24, - colorFilter: const ColorFilter.mode(Colors.black12, BlendMode.srcIn), + colorFilter: disabled ? const ColorFilter.mode(Colors.black12, BlendMode.srcIn) : const ColorFilter.mode(Colors.black, BlendMode.srcIn), ), ), ), @@ -659,7 +731,7 @@ class _ProfileScreenState extends State { for (int i = 0; i < state.profile.badges.length; i++) ...[ state.profile.badges[i].image.contains('svg') ? Tooltip( - message: state.profile.badges[i].description, + message: state.profile.badges[i].description, triggerMode: TooltipTriggerMode.tap, textStyle: const TextStyle( color: Colors.black,