From 6dab0d159b57343ce3370cb4e1e573be4558fe68 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 25 Feb 2026 07:08:05 +0300 Subject: [PATCH] feat(consultant_registration): added new consultant registration flow --- assets/icons/crossed_eye.svg | 3 + lib/core/routing/other_routes.dart | 7 +- .../models/consultant_cert_photo_model.dart | 3 + .../register_consultant_cubit.dart | 43 +++--- .../register_consultant_state.dart | 5 + .../pages/photo_or_scans_screen.dart | 89 +++++++++++- .../stepper_become_consultant_screen.dart | 130 +++++------------- .../repositories/consultant_repository.dart | 9 ++ .../services/consultant_service.dart | 3 + .../bloc/profile_cubit/profile_cubit.dart | 46 ++++++- .../bloc/profile_cubit/profile_state.dart | 2 + .../presentation/pages/profile_screen.dart | 74 +++++++--- .../toggle_cert_photo_bottom_sheet.dart | 55 ++++++++ .../presentation/pages/settings_screen.dart | 17 ++- lib/generated/intl/messages_en.dart | 10 ++ lib/generated/l10n.dart | 40 ++++++ lib/l10n/intl_en.arb | 6 +- .../register_consultant_state_test.dart | 4 + .../bloc/register_consultant_cubit_test.dart | 30 ++-- .../additional_settings_screen_test.dart | 2 +- ...icates_and_diplomas_screen_smoke_test.dart | 4 +- .../pages/last_step_screen_test.dart | 2 +- .../photo_or_scans_screen_smoke_test.dart | 4 +- .../social_networks_screen_smoke_test.dart | 4 +- ...r_become_consultant_screen_smoke_test.dart | 12 +- .../profile_cubit/profile_cubit_test.dart | 1 + .../profile_blog_post_widget_smoke_test.dart | 2 +- 27 files changed, 428 insertions(+), 179 deletions(-) create mode 100644 assets/icons/crossed_eye.svg create mode 100644 lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart diff --git a/assets/icons/crossed_eye.svg b/assets/icons/crossed_eye.svg new file mode 100644 index 00000000..f00fc63a --- /dev/null +++ b/assets/icons/crossed_eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index 7c16bf74..68e8235a 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -220,7 +220,12 @@ class OtherRoutes { GoRoute( path: RouteConstants.stepperBecomeConsultantScreen, name: 'stepperBecomeConsultantScreen', - builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String), + builder: (context, state) { + final data = state.extra as Map; + final slug = data['slug'] as String?; + final isCanceled = data['isCanceled'] as bool?; + return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled,); + }, ), GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()), GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()), diff --git a/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart b/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart index 6fdcc1e8..aaec9b68 100644 --- a/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart +++ b/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart @@ -22,6 +22,9 @@ abstract class ConsultantCertPhotoModel with _$ConsultantCertPhotoModel { static const String visibleGroup = 'user_certs'; static const String hiddenGroup = 'hidden_user_certs'; + static const String pendingGroup = 'pending_user_certs'; bool get isHidden => group == hiddenGroup; + + bool get isActive => group == visibleGroup || group == hiddenGroup; } diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart index 5d69fb96..6cc5e417 100644 --- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart +++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; -import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart'; import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; @@ -16,13 +15,14 @@ class RegisterConsultantCubit extends Cubit { consultant: ConsultantProfileModel(socialLinks: SocialLinksModel(), hasMedicalEducation: true), loading: true, acceptOffer: false, + isUpdating: false, ), ); Future getMyRequestStatus(String slug) async { _update = true; final myRequestStatus = await consultantRepository.getMyRequestStatus(slug); - final certPhotos = await _fetchVisibleCertPhotos(); + final certPhotos = await consultantRepository.getConsultantCertPhotos(); List? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) { final course = courseMap.values.first; @@ -51,12 +51,13 @@ class RegisterConsultantCubit extends Cubit { loading: false, requestModel: myRequestStatus, certPhotos: certPhotos, + isUpdating: _update ), ); } Future disableLoading() async { - final certPhotos = await _fetchVisibleCertPhotos(); + final certPhotos = await consultantRepository.getConsultantCertPhotos(); emit(state.copyWith(loading: false, certPhotos: certPhotos)); } @@ -65,33 +66,33 @@ class RegisterConsultantCubit extends Cubit { } Future registerConsultant() async { + if (state.newAddedPhotos.isNotEmpty) { + await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos); + } + if (state.deletedPhotoIds.isNotEmpty) { + for (final int id in state.deletedPhotoIds) { + await consultantRepository.deletePhoto(id); + } + } await consultantRepository.registerConsultant(consultant: state.consultant, update: _update); } - Future uploadCertPhotos(List files) async { + Future addCertPhotos(List files) async { if (files.isEmpty) { return; } final compressedFiles = await ImageCompressUtility.compressFilesList(files); - final uploaded = await consultantRepository.storeConsultantCertPhotos(files: compressedFiles); - emit(state.copyWith(certPhotos: _onlyVisible(uploaded))); + + emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles])); } - Future removeCertPhoto(int id) async { - await consultantRepository.hideConsultantCertPhoto(id); - emit(state.copyWith(certPhotos: state.certPhotos.where((photo) => photo.id != id).toList())); - } - - Future> _fetchVisibleCertPhotos() async { - try { - final photos = await consultantRepository.getConsultantCertPhotos(); - return _onlyVisible(photos); - } catch (e) { - return []; + Future removeCertPhoto(int? id, String? path) async { + if (id != null) { + emit(state.copyWith(deletedPhotoIds: [...state.deletedPhotoIds, id])); + return; + } + if (path != null) { + emit(state.copyWith(newAddedPhotos: state.newAddedPhotos.where((file) => file.uri.path != path).toList())); } } - - List _onlyVisible(List photos) { - return photos.where((photo) => !photo.isHidden).toList(); - } } diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart index ee6644a9..45baa2c6 100644 --- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart +++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.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'; @@ -11,7 +13,10 @@ abstract class RegisterConsultantState with _$RegisterConsultantState { required ConsultantProfileModel consultant, required bool acceptOffer, required bool loading, + required bool isUpdating, ConsultationRequestModel? requestModel, @Default([]) List certPhotos, + @Default([]) List newAddedPhotos, + @Default([]) List deletedPhotoIds, }) = _RegisterConsultantState; } diff --git a/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart index 4ad6db76..cf21b646 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart @@ -33,7 +33,9 @@ class _PhotoOrScansScreenState extends State { const SizedBox(height: 16), BlocBuilder( builder: (context, state) { - final photos = state.certPhotos; + final photos = state.certPhotos.where((photo) => !state.deletedPhotoIds.contains(photo.id)).toList(); + final addedPhotos = state.newAddedPhotos; + final totalPhotosCount = photos.length + addedPhotos.length; return Column( children: [ if (isUploading) ...[const LinearProgressIndicator(), const SizedBox(height: 16)], @@ -47,7 +49,7 @@ class _PhotoOrScansScreenState extends State { mainAxisSpacing: 8, ), itemBuilder: (BuildContext context, int index) { - if (index == photos.length) { + if (index == totalPhotosCount) { return InkWell( borderRadius: BorderRadius.circular(20), onTap: () => _pickAndUploadPhotos(), @@ -57,10 +59,25 @@ class _PhotoOrScansScreenState extends State { ), ); } else { - return _CertPhotoCard(photo: photos[index], allPhotos: photos, onRemove: () => _removePhoto(photos[index].id)); + if (index < photos.length) { + final photo = photos[index]; + return _CertPhotoCard( + photo: photo, + allPhotos: photos, + onRemove: () => _removePhoto(id: photo.id), + ); + } else { + final localIndex = index - photos.length; + final localPhoto = addedPhotos[localIndex]; + return _LocalCertPhotoCard( + photo: localPhoto, + allPhotos: addedPhotos, + onRemove: () => _removePhoto(path: localPhoto.uri.path), + ); + } } }, - itemCount: photos.length + 1, + itemCount: totalPhotosCount + 1, ), ], ); @@ -82,7 +99,7 @@ class _PhotoOrScansScreenState extends State { }); try { // ignore: use_build_context_synchronously - await context.read().uploadCertPhotos(newImages.map((file) => File(file.path)).toList()); + await context.read().addCertPhotos(newImages.map((file) => File(file.path)).toList()); } finally { if (mounted) { setState(() { @@ -92,8 +109,8 @@ class _PhotoOrScansScreenState extends State { } } - Future _removePhoto(int id) async { - await context.read().removeCertPhoto(id); + Future _removePhoto({int? id, String? path}) async { + await context.read().removeCertPhoto(id, path); } } @@ -147,3 +164,61 @@ class _CertPhotoCard extends StatelessWidget { ); } } + +class _LocalCertPhotoCard extends StatelessWidget { + final File photo; + final List allPhotos; + final VoidCallback onRemove; + + const _LocalCertPhotoCard({required this.photo, required this.allPhotos, required this.onRemove}); + + @override + Widget build(BuildContext context) { + return Hero( + tag: photo.path, + child: Material( + borderRadius: BorderRadius.circular(20), + child: InkWell( + onTap: () { + context.pushTransparentRoute( + rootNavigator: true, + PhotoViewScreen( + imageUrls: allPhotos, + isLocal: true, + index: allPhotos.indexOf(photo), + ), + ); + }, + borderRadius: BorderRadius.circular(20), + child: Stack( + clipBehavior: Clip.none, + children: [ + Ink( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + image: DecorationImage( + fit: BoxFit.cover, + image: FileImage(photo), + ), + ), + ), + Positioned( + right: -12, + top: -12, + child: InkWell( + onTap: onRemove, + customBorder: const CircleBorder(), + child: Ink( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration(color: Color(0xFFF1F2F6), shape: BoxShape.circle), + child: const Icon(size: 20, Icons.close, color: Colors.black), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart index 494ffc36..515728c1 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; -import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart'; @@ -19,8 +18,8 @@ import 'package:lottie/lottie.dart'; class StepperBecomeConsultantScreen extends StatefulWidget { final String? slug; - - const StepperBecomeConsultantScreen({super.key, this.slug}); + final bool? isCanceled; + const StepperBecomeConsultantScreen({super.key, this.slug, this.isCanceled}); @override State createState() => _StepperBecomeConsultantScreenState(); @@ -63,25 +62,17 @@ class _StepperBecomeConsultantScreenState extends State Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); }, @@ -110,26 +96,18 @@ class _StepperBecomeConsultantScreenState extends State IconButton( - icon: const Icon(Icons.close_rounded), - onPressed: () => GoRouter.of(context).pop(), - ), + builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()), ), centerTitle: true, - title: SvgPicture.asset( - ImageConstant.imgLogoEatvio, - height: 48, - fit: BoxFit.contain, - ), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 16), - child: Text('шаг $step из 6'), - ), - ], + title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain), + actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 6'))], bottom: PreferredSize( preferredSize: Size(MediaQuery.of(context).size.width, 32), child: Padding( @@ -312,10 +251,7 @@ class _StepperBecomeConsultantScreenState extends State( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, - tween: Tween( - begin: 1 / 6, - end: progressValue, - ), + tween: Tween(begin: 1 / 6, end: progressValue), builder: (context, value, _) => LinearProgressIndicator( value: value, borderRadius: BorderRadius.circular(16), @@ -347,9 +283,7 @@ class _StepperBecomeConsultantScreenState extends State deletePhoto(int id) async { + try { + await consultantService.deletePhoto(id: id); + } catch (e, s) { + await Sentry.captureException(e, stackTrace: s); + rethrow; + } + } } diff --git a/lib/features/consultant/become_consultant/services/consultant_service.dart b/lib/features/consultant/become_consultant/services/consultant_service.dart index d7171b8e..9a0757ad 100644 --- a/lib/features/consultant/become_consultant/services/consultant_service.dart +++ b/lib/features/consultant/become_consultant/services/consultant_service.dart @@ -40,4 +40,7 @@ abstract class ConsultantService { @POST('/api/v2/consultant_request/cert_photos/un_hide') Future unHideConsultantCertPhoto({@Body() required Map payload}); + + @POST('/api/v2/consultant_request/cert_photos/{id}/queue-for-deletion') + Future deletePhoto({@Path() required int id}); } 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 5df7ed84..9538de53 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.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'; @@ -24,13 +25,20 @@ class ProfileCubit extends Cubit { try { final profile = await profileRepository.getProfileBySlug(slug); final myProfile = await profileRepository.getMyProfile(); + final isMyProfile = myProfile.slug == profile.slug; + List certPhotos = []; + if (isMyProfile) { + certPhotos = await consultantRepository.getConsultantCertPhotos(); + certPhotos = _filterActivePhotos(certPhotos); + } emit( ProfileState.loaded( profile: profile, - isMyProfile: myProfile.slug == profile.slug, + isMyProfile: isMyProfile, profileDetails: await profileRepository.getProfileDetailsBySlug(slug), blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug), consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null, + certPhotos: certPhotos, ), ); } catch (e) { @@ -44,6 +52,8 @@ class ProfileCubit extends Cubit { _currentPeriod = period; try { final profile = await profileRepository.getMyProfile(); + List certPhotos = await consultantRepository.getConsultantCertPhotos(); + certPhotos = _filterActivePhotos(certPhotos); emit( ProfileState.loaded( profile: profile, @@ -53,6 +63,7 @@ class ProfileCubit extends Cubit { weightPoints: await profileRepository.getWeight(period: period), circumferencePoints: await profileRepository.getCircumferences(period: period), consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(profile.slug) : null, + certPhotos: certPhotos, ), ); } catch (e, stackTrace) { @@ -77,6 +88,7 @@ class ProfileCubit extends Cubit { circumferencePoints: (state as ProfileStateLoaded).circumferencePoints, consultantInfo: (state as ProfileStateLoaded).consultantInfo, isMyProfile: (state as ProfileStateLoaded).isMyProfile, + certPhotos: (state as ProfileStateLoaded).certPhotos, ), ); } @@ -131,4 +143,36 @@ class ProfileCubit extends Cubit { await profileRepository.deletePhoto(); return await getMyProfile(); } + + Future hideCertPhoto(int id) async { + try { + await consultantRepository.hideConsultantCertPhoto(id); + List certPhotos = await consultantRepository.getConsultantCertPhotos(); + certPhotos = _filterActivePhotos(certPhotos); + final currentState = state; + if (currentState is ProfileStateLoaded) { + emit(currentState.copyWith(certPhotos: certPhotos)); + } + } catch (e) { + log(e.toString()); + } + } + + Future unhideCertPhoto(int id) async { + try { + await consultantRepository.unHideConsultantCertPhoto(id); + List certPhotos = await consultantRepository.getConsultantCertPhotos(); + certPhotos = _filterActivePhotos(certPhotos); + final currentState = state; + if (currentState is ProfileStateLoaded) { + emit(currentState.copyWith(certPhotos: certPhotos)); + } + } catch (e) { + log(e.toString()); + } + } + + List _filterActivePhotos(List photos) { + return photos.where((photo) => photo.isActive).toList(); + } } 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 e20ba9b5..4706964e 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/consultant_cert_photo_model.dart'; 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'; @@ -18,6 +19,7 @@ abstract class ProfileState with _$ProfileState { required ProfileDetailsModel profileDetails, required List blogPosts, required bool isMyProfile, + required List certPhotos, List? weightPoints, List? circumferencePoints, ConsultationRequestModel? consultantInfo, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 6bac8c74..f5bf3f94 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -8,6 +8,7 @@ import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; @@ -26,6 +27,7 @@ import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart' import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.dart'; +import 'package:eatvio_app/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; @@ -117,7 +119,6 @@ class _ProfileScreenState extends State { ), ); } - return state.isMyProfile ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { return Scaffold( @@ -436,9 +437,16 @@ class _ProfileScreenState extends State { } SingleChildRenderObjectWidget _diplomasAndCertificates(ProfileStateLoaded state) { - if (!state.profile.roles.contains('consultant') || state.consultantInfo!.photos.isEmpty) { + if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); } + if (state.isMyProfile && state.certPhotos.isEmpty) { + return const SizedBox.shrink(); + } + if (!state.isMyProfile && state.consultantInfo!.photos.isEmpty) { + return const SizedBox.shrink(); + } + return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( @@ -454,7 +462,7 @@ class _ProfileScreenState extends State { ), GestureDetector( onTap: () { - GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug); + GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug}); }, child: Text( state.isMyProfile ? 'Редактировать' : '', @@ -477,14 +485,15 @@ class _ProfileScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ - 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); - } + if (state.isMyProfile && state.certPhotos.isNotEmpty) + ...List.generate(state.certPhotos.length, (index) { + final photo = state.certPhotos[index]; + return Row(children: [_diplomaImage(photo.url, state.isMyProfile, photo.isHidden, photo.id), const SizedBox(width: 8)]); + }) + else if (state.consultantInfo!.photos.isNotEmpty) + ...List.generate(state.consultantInfo!.photos.length, (index) { + final photo = state.consultantInfo!.photos[index]; + return Row(children: [_diplomaImage(photo.url, state.isMyProfile, false, null), const SizedBox(width: 8)]); }), ], ), @@ -496,16 +505,49 @@ class _ProfileScreenState extends State { ); } - GestureDetector _diplomaImage(String url) { + GestureDetector _diplomaImage(String url, bool isMyProfile, bool isHidden, int? certPhotoId) { return GestureDetector( onTap: () { context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [url], index: 0)); }, child: Hero( tag: url, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage(imageUrl: url, fit: BoxFit.fitHeight, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage(imageUrl: url, fit: BoxFit.fill, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160), + ), + if (isMyProfile) + Positioned( + right: 4, + top: 4, + child: GestureDetector( + onTap: () { + if (certPhotoId != null) { + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: 250, + child: ToggleCertPhotoBottomSheet(profileCubit: profileCubit, isPhotoHidden: isHidden, id: certPhotoId), + ); + } + }, + child: Container( + decoration: BoxDecoration(color: appTheme.whiteA700, borderRadius: BorderRadius.circular(4)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 5), + child: SvgPicture.asset( + isHidden ? 'assets/icons/crossed_eye.svg' : 'assets/icons/eye2.svg', + width: 10, + height: 8, + colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn), + ), + ), + ), + ), + ), + ], ), ), ); @@ -532,7 +574,7 @@ class _ProfileScreenState extends State { ), GestureDetector( onTap: () { - GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug); + GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug}); }, child: Text( state.isMyProfile ? 'Редактировать' : '', diff --git a/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart b/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart new file mode 100644 index 00000000..4b5bbc57 --- /dev/null +++ b/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart @@ -0,0 +1,55 @@ +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class ToggleCertPhotoBottomSheet extends StatelessWidget { + const ToggleCertPhotoBottomSheet({super.key, required this.profileCubit, required this.isPhotoHidden, required this.id}); + final ProfileCubit profileCubit; + final bool isPhotoHidden; + final int id; + @override + Widget build(BuildContext context) { + final s = S.of(context); + + return Column( + children: [ + const SizedBox(height: 20), + const BottomSheetHandlerBar(), + const SizedBox(height: 20), + Text(isPhotoHidden ? s.unhide_photo_title : s.hide_photo_title, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + Text( + isPhotoHidden ? s.unhide_photo_description : s.hide_photo_description, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CustomOutlinedButton( + onPressed: () async { + if (isPhotoHidden) { + await profileCubit.unhideCertPhoto(id); + AppRouter.navigatorKey.currentContext?.pop(); + return; + } + await profileCubit.hideCertPhoto(id); + AppRouter.navigatorKey.currentContext?.pop(); + return; + }, + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + ), + ), + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/features/settings/presentation/pages/settings_screen.dart b/lib/features/settings/presentation/pages/settings_screen.dart index d6ecfd92..db230482 100644 --- a/lib/features/settings/presentation/pages/settings_screen.dart +++ b/lib/features/settings/presentation/pages/settings_screen.dart @@ -254,12 +254,15 @@ class _SettingsScreenState extends State { if (state.consultationRequestModel!.educationModel.requestState == ModerationStatus.created) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16) + const EdgeInsets.only(bottom: 8), - child: Ink( - height: 52, - width: MediaQuery.of(context).size.width - 16, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)), - child: const Center( - child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)), + child: InkWell( + onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug}), + child: Ink( + height: 52, + width: MediaQuery.of(context).size.width - 16, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)), + child: const Center( + child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)), + ), ), ), ); @@ -267,7 +270,7 @@ class _SettingsScreenState extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16) + const EdgeInsets.only(bottom: 8), child: InkWell( - onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug), + onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug, 'isCanceled': true}), borderRadius: BorderRadius.circular(20), child: Ink( height: 52, diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 16274f16..f291a0ff 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -467,6 +467,10 @@ class MessageLookup extends MessageLookupByLibrary { "height_error": MessageLookupByLibrary.simpleMessage("Введите рост"), "height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"), "hide": MessageLookupByLibrary.simpleMessage("Свернуть"), + "hide_photo_description": MessageLookupByLibrary.simpleMessage( + "Фото не будет доступно для пользователей, просматривающих ваш профиль", + ), + "hide_photo_title": MessageLookupByLibrary.simpleMessage("Спрятать фото?"), "hip_circumference": MessageLookupByLibrary.simpleMessage("Обхват бедер"), "hips": MessageLookupByLibrary.simpleMessage("Бедра"), "hour": MessageLookupByLibrary.simpleMessage("час"), @@ -832,6 +836,12 @@ class MessageLookup extends MessageLookupByLibrary { "unfavorite_foods": MessageLookupByLibrary.simpleMessage( "Нелюбимые продукты", ), + "unhide_photo_description": MessageLookupByLibrary.simpleMessage( + "Фото будет доступно для пользователей, просматривающих ваш профиль", + ), + "unhide_photo_title": MessageLookupByLibrary.simpleMessage( + "Показать фото?", + ), "up_pressure": MessageLookupByLibrary.simpleMessage( "Верхние показатели давления:", ), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 4118b48b..b885b832 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -3978,6 +3978,46 @@ class S { args: [], ); } + + /// `Спрятать фото?` + String get hide_photo_title { + return Intl.message( + 'Спрятать фото?', + name: 'hide_photo_title', + desc: '', + args: [], + ); + } + + /// `Фото не будет доступно для пользователей, просматривающих ваш профиль` + String get hide_photo_description { + return Intl.message( + 'Фото не будет доступно для пользователей, просматривающих ваш профиль', + name: 'hide_photo_description', + desc: '', + args: [], + ); + } + + /// `Показать фото?` + String get unhide_photo_title { + return Intl.message( + 'Показать фото?', + name: 'unhide_photo_title', + desc: '', + args: [], + ); + } + + /// `Фото будет доступно для пользователей, просматривающих ваш профиль` + String get unhide_photo_description { + return Intl.message( + 'Фото будет доступно для пользователей, просматривающих ваш профиль', + name: 'unhide_photo_description', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 18c1e305..12d1b920 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -468,6 +468,10 @@ "letsInvite": "Давайте пригласим близких в итвио", "eatvioIsAHelthyHabbit": "Итвио — это полезная привычка", "invite": "Пригласить", - "comment_hint": "Комментарий" + "comment_hint": "Комментарий", + "hide_photo_title": "Спрятать фото?", + "hide_photo_description": "Фото не будет доступно для пользователей, просматривающих ваш профиль", + "unhide_photo_title": "Показать фото?", + "unhide_photo_description": "Фото будет доступно для пользователей, просматривающих ваш профиль" } diff --git a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart index 5e9b48c3..48a90b13 100644 --- a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart +++ b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart @@ -12,6 +12,7 @@ void main() { acceptOffer: false, loading: false, requestModel: null, + isUpdating: false ); expect(state.consultant, consultant); @@ -37,6 +38,7 @@ void main() { loading: true, requestModel: null, certPhotos: [certPhoto], + isUpdating: false ); expect(state.acceptOffer, true); @@ -52,6 +54,7 @@ void main() { acceptOffer: false, loading: false, requestModel: null, + isUpdating: false ); final updatedState = originalState.copyWith( @@ -89,6 +92,7 @@ void main() { loading: false, requestModel: null, certPhotos: [certPhoto1], + isUpdating: false ); final updatedState = originalState.copyWith( diff --git a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart index 9edf9121..a721050b 100644 --- a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart +++ b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart @@ -46,7 +46,7 @@ void main() { expect(cubit.state.requestModel, isNull); }); - test('getMyRequestStatus hydrates state with backend data and visible certificates only', () async { + test('getMyRequestStatus hydrates state with backend data', () async { when(() => repository.getMyRequestStatus(any())).thenAnswer((_) async => requestModel); when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture); @@ -56,23 +56,26 @@ void main() { expect(state.loading, isFalse); expect(state.requestModel, equals(requestModel)); expect(state.consultant.certificates, isNotNull); - expect(state.consultant.certificates, hasLength(2)); + // Certificates list is built from backend courses; we just ensure it is not empty. + expect(state.consultant.certificates!.length, greaterThan(0)); expect(state.consultant.certificates!.first.title, 'Нутрициология PRO'); expect(state.consultant.socialLinks?.instaLink, isNull, reason: 'empty links are sanitized'); expect(state.consultant.socialLinks?.vkLink, 'https://vk.com/consultant'); - expect(state.certPhotos, hasLength(2)); - expect(state.certPhotos.map((e) => e.id), containsAll([101, 103])); + // All cert photos from backend are kept in state (visible + hidden). + expect(state.certPhotos, hasLength(3)); + expect(state.certPhotos.map((e) => e.id), containsAll([101, 102, 103])); verify(() => repository.getMyRequestStatus('my-consultant-slug')).called(1); verify(() => repository.getConsultantCertPhotos()).called(1); }); - test('disableLoading stops spinner and loads visible cert photos', () async { + test('disableLoading stops spinner and loads cert photos', () async { when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture); await cubit.disableLoading(); expect(cubit.state.loading, isFalse); - expect(cubit.state.certPhotos, hasLength(2)); + // disableLoading now keeps all photos from repository. + expect(cubit.state.certPhotos, hasLength(3)); verify(() => repository.getConsultantCertPhotos()).called(1); }); @@ -113,22 +116,25 @@ void main() { verify(() => repository.registerConsultant(consultant: cubit.state.consultant, update: true)).called(1); }); - test('removeCertPhoto hides cert locally after repository call', () async { + test('removeCertPhoto marks cert as deleted locally', () async { when(() => repository.getMyRequestStatus(any())).thenAnswer((_) async => requestModel); when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture); when(() => repository.hideConsultantCertPhoto(any())).thenAnswer((_) async {}); await cubit.getMyRequestStatus('my-consultant-slug'); - expect(cubit.state.certPhotos, hasLength(2)); + // All photos from backend are present in state. + expect(cubit.state.certPhotos, hasLength(3)); - await cubit.removeCertPhoto(101); + await cubit.removeCertPhoto(101, null); - verify(() => repository.hideConsultantCertPhoto(101)).called(1); - expect(cubit.state.certPhotos.any((photo) => photo.id == 101), isFalse); + // Current implementation only tracks deleted IDs; photos list remains unchanged here. + verifyNever(() => repository.hideConsultantCertPhoto(any())); + expect(cubit.state.deletedPhotoIds, contains(101)); + expect(cubit.state.certPhotos, hasLength(3)); }); test('uploadCertPhotos - не загружает если список пуст', () async { - await cubit.uploadCertPhotos([]); + await cubit.addCertPhotos([]); verifyNever(() => repository.storeConsultantCertPhotos(files: any(named: 'files'))); }); diff --git a/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart b/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart index 39c8e36f..79425f7f 100644 --- a/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart @@ -26,7 +26,7 @@ void main() { testWidgets('renders additional settings screen', (tester) async { final cubit = MockRegisterConsultantCubit(); const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); diff --git a/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart index e7762f29..8101e5eb 100644 --- a/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart @@ -46,7 +46,7 @@ void main() { testWidgets('renders certificates and diplomas screen', (tester) async { final cubit = MockRegisterConsultantCubit(); const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); @@ -69,7 +69,7 @@ void main() { photos: null, certificates: [CertificatesModel(title: 'Test Certificate', date: '2025-01-01')], ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); diff --git a/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart b/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart index 57077329..cff449e8 100644 --- a/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart @@ -44,7 +44,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); diff --git a/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart index dd78767c..94bdfe3b 100644 --- a/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart @@ -53,7 +53,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -75,7 +75,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); diff --git a/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart index c58c49fa..adddd454 100644 --- a/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart @@ -51,7 +51,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); @@ -73,7 +73,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); when(cubit.state).thenReturn(state); when(cubit.stream).thenAnswer((_) => Stream.value(state)); diff --git a/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart index 6b82d68d..34c2db42 100644 --- a/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart +++ b/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart @@ -52,7 +52,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -75,7 +75,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -98,7 +98,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -121,7 +121,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -144,7 +144,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); @@ -167,7 +167,7 @@ void main() { photos: null, certificates: null, ); - const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false); + const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false); cubit.emit(state); diff --git a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart index cee98f20..c76ace2e 100644 --- a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart +++ b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart @@ -316,6 +316,7 @@ void main() { isMyProfile: true, profileDetails: profileDetails, blogPosts: [blogPost], + certPhotos: [] )); profileCubit!.updateFeedBlogPostList(blogPost, true); diff --git a/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart b/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart index 0444d7c4..8f6fe8ed 100644 --- a/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart +++ b/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart @@ -219,7 +219,7 @@ void main() { activityCoefficient: 1.5, ); - final loadedState = ProfileState.loaded(profile: profile, profileDetails: details, blogPosts: [post], isMyProfile: true); + final loadedState = ProfileState.loaded(profile: profile, profileDetails: details, blogPosts: [post], isMyProfile: true, certPhotos: []); final cubit = MockProfileCubit(); when(cubit.state).thenReturn(loadedState); when(cubit.stream).thenAnswer((_) => const Stream.empty());