diff --git a/android/app/build.gradle b/android/app/build.gradle index 81f74f06..46a74368 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ plugins { android { namespace = "com.eatvioapp.app" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "25.1.8937393" compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/lib/features/consultant/become_consultant/models/consultant_profile_model.dart b/lib/features/consultant/become_consultant/models/consultant_profile_model.dart index 4cbde94f..2298d812 100644 --- a/lib/features/consultant/become_consultant/models/consultant_profile_model.dart +++ b/lib/features/consultant/become_consultant/models/consultant_profile_model.dart @@ -7,10 +7,10 @@ part 'consultant_profile_model.g.dart'; @freezed class ConsultantProfileModel with _$ConsultantProfileModel { const factory ConsultantProfileModel({ - @JsonKey(name: 'has_medical_education') bool? hasMedicalEducation, - @JsonKey(name: 'social_links') SocialLinksModel? socialLinks, - @JsonKey(name: 'cert_photos') List? photos, - @JsonKey(name: 'completed_courses') List? certificates, + @JsonKey(name: 'has_medical_education', includeIfNull: false) bool? hasMedicalEducation, + @JsonKey(name: 'social_links', includeIfNull: false) SocialLinksModel? socialLinks, + @JsonKey(name: 'cert_photos', includeIfNull: false) List? photos, + @JsonKey(name: 'completed_courses', includeIfNull: false) List? certificates, }) = _ConsultantProfileModel; factory ConsultantProfileModel.fromJson(Map json) => _$ConsultantProfileModelFromJson(json); @@ -19,11 +19,11 @@ class ConsultantProfileModel with _$ConsultantProfileModel { @freezed class SocialLinksModel with _$SocialLinksModel { const factory SocialLinksModel({ - @JsonKey(name: 'vk_link') String? vkLink, - @JsonKey(name: 'tg_link') String? tgLink, - @JsonKey(name: 'fb_link') String? fbLink, - @JsonKey(name: 'insta_link') String? instaLink, - @JsonKey(name: 'personal_site_link') String? personalSiteLink, + @JsonKey(name: 'vk_link', includeIfNull: false) String? vkLink, + @JsonKey(name: 'tg_link', includeIfNull: false) String? tgLink, + @JsonKey(name: 'fb_link', includeIfNull: false) String? fbLink, + @JsonKey(name: 'insta_link', includeIfNull: false) String? instaLink, + @JsonKey(name: 'personal_site_link', includeIfNull: false) String? personalSiteLink, }) = _SocialLinksModel; factory SocialLinksModel.fromJson(Map json) => _$SocialLinksModelFromJson(json); diff --git a/lib/features/consultant/become_consultant/models/consultant_profile_model.freezed.dart b/lib/features/consultant/become_consultant/models/consultant_profile_model.freezed.dart index 3abdd043..d320c937 100644 --- a/lib/features/consultant/become_consultant/models/consultant_profile_model.freezed.dart +++ b/lib/features/consultant/become_consultant/models/consultant_profile_model.freezed.dart @@ -21,13 +21,13 @@ ConsultantProfileModel _$ConsultantProfileModelFromJson( /// @nodoc mixin _$ConsultantProfileModel { - @JsonKey(name: 'has_medical_education') + @JsonKey(name: 'has_medical_education', includeIfNull: false) bool? get hasMedicalEducation => throw _privateConstructorUsedError; - @JsonKey(name: 'social_links') + @JsonKey(name: 'social_links', includeIfNull: false) SocialLinksModel? get socialLinks => throw _privateConstructorUsedError; - @JsonKey(name: 'cert_photos') + @JsonKey(name: 'cert_photos', includeIfNull: false) List? get photos => throw _privateConstructorUsedError; - @JsonKey(name: 'completed_courses') + @JsonKey(name: 'completed_courses', includeIfNull: false) List? get certificates => throw _privateConstructorUsedError; @@ -48,10 +48,12 @@ abstract class $ConsultantProfileModelCopyWith<$Res> { _$ConsultantProfileModelCopyWithImpl<$Res, ConsultantProfileModel>; @useResult $Res call( - {@JsonKey(name: 'has_medical_education') bool? hasMedicalEducation, - @JsonKey(name: 'social_links') SocialLinksModel? socialLinks, - @JsonKey(name: 'cert_photos') List? photos, - @JsonKey(name: 'completed_courses') + {@JsonKey(name: 'has_medical_education', includeIfNull: false) + bool? hasMedicalEducation, + @JsonKey(name: 'social_links', includeIfNull: false) + SocialLinksModel? socialLinks, + @JsonKey(name: 'cert_photos', includeIfNull: false) List? photos, + @JsonKey(name: 'completed_courses', includeIfNull: false) List? certificates}); $SocialLinksModelCopyWith<$Res>? get socialLinks; @@ -123,10 +125,12 @@ abstract class _$$ConsultantProfileModelImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'has_medical_education') bool? hasMedicalEducation, - @JsonKey(name: 'social_links') SocialLinksModel? socialLinks, - @JsonKey(name: 'cert_photos') List? photos, - @JsonKey(name: 'completed_courses') + {@JsonKey(name: 'has_medical_education', includeIfNull: false) + bool? hasMedicalEducation, + @JsonKey(name: 'social_links', includeIfNull: false) + SocialLinksModel? socialLinks, + @JsonKey(name: 'cert_photos', includeIfNull: false) List? photos, + @JsonKey(name: 'completed_courses', includeIfNull: false) List? certificates}); @override @@ -178,10 +182,12 @@ class __$$ConsultantProfileModelImplCopyWithImpl<$Res> @JsonSerializable() class _$ConsultantProfileModelImpl implements _ConsultantProfileModel { const _$ConsultantProfileModelImpl( - {@JsonKey(name: 'has_medical_education') this.hasMedicalEducation, - @JsonKey(name: 'social_links') this.socialLinks, - @JsonKey(name: 'cert_photos') final List? photos, - @JsonKey(name: 'completed_courses') + {@JsonKey(name: 'has_medical_education', includeIfNull: false) + this.hasMedicalEducation, + @JsonKey(name: 'social_links', includeIfNull: false) this.socialLinks, + @JsonKey(name: 'cert_photos', includeIfNull: false) + final List? photos, + @JsonKey(name: 'completed_courses', includeIfNull: false) final List? certificates}) : _photos = photos, _certificates = certificates; @@ -190,14 +196,14 @@ class _$ConsultantProfileModelImpl implements _ConsultantProfileModel { _$$ConsultantProfileModelImplFromJson(json); @override - @JsonKey(name: 'has_medical_education') + @JsonKey(name: 'has_medical_education', includeIfNull: false) final bool? hasMedicalEducation; @override - @JsonKey(name: 'social_links') + @JsonKey(name: 'social_links', includeIfNull: false) final SocialLinksModel? socialLinks; final List? _photos; @override - @JsonKey(name: 'cert_photos') + @JsonKey(name: 'cert_photos', includeIfNull: false) List? get photos { final value = _photos; if (value == null) return null; @@ -208,7 +214,7 @@ class _$ConsultantProfileModelImpl implements _ConsultantProfileModel { final List? _certificates; @override - @JsonKey(name: 'completed_courses') + @JsonKey(name: 'completed_courses', includeIfNull: false) List? get certificates { final value = _certificates; if (value == null) return null; @@ -264,27 +270,30 @@ class _$ConsultantProfileModelImpl implements _ConsultantProfileModel { abstract class _ConsultantProfileModel implements ConsultantProfileModel { const factory _ConsultantProfileModel( - {@JsonKey(name: 'has_medical_education') final bool? hasMedicalEducation, - @JsonKey(name: 'social_links') final SocialLinksModel? socialLinks, - @JsonKey(name: 'cert_photos') final List? photos, - @JsonKey(name: 'completed_courses') - final List? - certificates}) = _$ConsultantProfileModelImpl; + {@JsonKey(name: 'has_medical_education', includeIfNull: false) + final bool? hasMedicalEducation, + @JsonKey(name: 'social_links', includeIfNull: false) + final SocialLinksModel? socialLinks, + @JsonKey(name: 'cert_photos', includeIfNull: false) + final List? photos, + @JsonKey(name: 'completed_courses', includeIfNull: false) + final List? certificates}) = + _$ConsultantProfileModelImpl; factory _ConsultantProfileModel.fromJson(Map json) = _$ConsultantProfileModelImpl.fromJson; @override - @JsonKey(name: 'has_medical_education') + @JsonKey(name: 'has_medical_education', includeIfNull: false) bool? get hasMedicalEducation; @override - @JsonKey(name: 'social_links') + @JsonKey(name: 'social_links', includeIfNull: false) SocialLinksModel? get socialLinks; @override - @JsonKey(name: 'cert_photos') + @JsonKey(name: 'cert_photos', includeIfNull: false) List? get photos; @override - @JsonKey(name: 'completed_courses') + @JsonKey(name: 'completed_courses', includeIfNull: false) List? get certificates; /// Create a copy of ConsultantProfileModel @@ -301,15 +310,15 @@ SocialLinksModel _$SocialLinksModelFromJson(Map json) { /// @nodoc mixin _$SocialLinksModel { - @JsonKey(name: 'vk_link') + @JsonKey(name: 'vk_link', includeIfNull: false) String? get vkLink => throw _privateConstructorUsedError; - @JsonKey(name: 'tg_link') + @JsonKey(name: 'tg_link', includeIfNull: false) String? get tgLink => throw _privateConstructorUsedError; - @JsonKey(name: 'fb_link') + @JsonKey(name: 'fb_link', includeIfNull: false) String? get fbLink => throw _privateConstructorUsedError; - @JsonKey(name: 'insta_link') + @JsonKey(name: 'insta_link', includeIfNull: false) String? get instaLink => throw _privateConstructorUsedError; - @JsonKey(name: 'personal_site_link') + @JsonKey(name: 'personal_site_link', includeIfNull: false) String? get personalSiteLink => throw _privateConstructorUsedError; /// Serializes this SocialLinksModel to a JSON map. @@ -329,11 +338,12 @@ abstract class $SocialLinksModelCopyWith<$Res> { _$SocialLinksModelCopyWithImpl<$Res, SocialLinksModel>; @useResult $Res call( - {@JsonKey(name: 'vk_link') String? vkLink, - @JsonKey(name: 'tg_link') String? tgLink, - @JsonKey(name: 'fb_link') String? fbLink, - @JsonKey(name: 'insta_link') String? instaLink, - @JsonKey(name: 'personal_site_link') String? personalSiteLink}); + {@JsonKey(name: 'vk_link', includeIfNull: false) String? vkLink, + @JsonKey(name: 'tg_link', includeIfNull: false) String? tgLink, + @JsonKey(name: 'fb_link', includeIfNull: false) String? fbLink, + @JsonKey(name: 'insta_link', includeIfNull: false) String? instaLink, + @JsonKey(name: 'personal_site_link', includeIfNull: false) + String? personalSiteLink}); } /// @nodoc @@ -391,11 +401,12 @@ abstract class _$$SocialLinksModelImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'vk_link') String? vkLink, - @JsonKey(name: 'tg_link') String? tgLink, - @JsonKey(name: 'fb_link') String? fbLink, - @JsonKey(name: 'insta_link') String? instaLink, - @JsonKey(name: 'personal_site_link') String? personalSiteLink}); + {@JsonKey(name: 'vk_link', includeIfNull: false) String? vkLink, + @JsonKey(name: 'tg_link', includeIfNull: false) String? tgLink, + @JsonKey(name: 'fb_link', includeIfNull: false) String? fbLink, + @JsonKey(name: 'insta_link', includeIfNull: false) String? instaLink, + @JsonKey(name: 'personal_site_link', includeIfNull: false) + String? personalSiteLink}); } /// @nodoc @@ -446,29 +457,30 @@ class __$$SocialLinksModelImplCopyWithImpl<$Res> @JsonSerializable() class _$SocialLinksModelImpl implements _SocialLinksModel { const _$SocialLinksModelImpl( - {@JsonKey(name: 'vk_link') this.vkLink, - @JsonKey(name: 'tg_link') this.tgLink, - @JsonKey(name: 'fb_link') this.fbLink, - @JsonKey(name: 'insta_link') this.instaLink, - @JsonKey(name: 'personal_site_link') this.personalSiteLink}); + {@JsonKey(name: 'vk_link', includeIfNull: false) this.vkLink, + @JsonKey(name: 'tg_link', includeIfNull: false) this.tgLink, + @JsonKey(name: 'fb_link', includeIfNull: false) this.fbLink, + @JsonKey(name: 'insta_link', includeIfNull: false) this.instaLink, + @JsonKey(name: 'personal_site_link', includeIfNull: false) + this.personalSiteLink}); factory _$SocialLinksModelImpl.fromJson(Map json) => _$$SocialLinksModelImplFromJson(json); @override - @JsonKey(name: 'vk_link') + @JsonKey(name: 'vk_link', includeIfNull: false) final String? vkLink; @override - @JsonKey(name: 'tg_link') + @JsonKey(name: 'tg_link', includeIfNull: false) final String? tgLink; @override - @JsonKey(name: 'fb_link') + @JsonKey(name: 'fb_link', includeIfNull: false) final String? fbLink; @override - @JsonKey(name: 'insta_link') + @JsonKey(name: 'insta_link', includeIfNull: false) final String? instaLink; @override - @JsonKey(name: 'personal_site_link') + @JsonKey(name: 'personal_site_link', includeIfNull: false) final String? personalSiteLink; @override @@ -514,30 +526,31 @@ class _$SocialLinksModelImpl implements _SocialLinksModel { abstract class _SocialLinksModel implements SocialLinksModel { const factory _SocialLinksModel( - {@JsonKey(name: 'vk_link') final String? vkLink, - @JsonKey(name: 'tg_link') final String? tgLink, - @JsonKey(name: 'fb_link') final String? fbLink, - @JsonKey(name: 'insta_link') final String? instaLink, - @JsonKey(name: 'personal_site_link') + {@JsonKey(name: 'vk_link', includeIfNull: false) final String? vkLink, + @JsonKey(name: 'tg_link', includeIfNull: false) final String? tgLink, + @JsonKey(name: 'fb_link', includeIfNull: false) final String? fbLink, + @JsonKey(name: 'insta_link', includeIfNull: false) + final String? instaLink, + @JsonKey(name: 'personal_site_link', includeIfNull: false) final String? personalSiteLink}) = _$SocialLinksModelImpl; factory _SocialLinksModel.fromJson(Map json) = _$SocialLinksModelImpl.fromJson; @override - @JsonKey(name: 'vk_link') + @JsonKey(name: 'vk_link', includeIfNull: false) String? get vkLink; @override - @JsonKey(name: 'tg_link') + @JsonKey(name: 'tg_link', includeIfNull: false) String? get tgLink; @override - @JsonKey(name: 'fb_link') + @JsonKey(name: 'fb_link', includeIfNull: false) String? get fbLink; @override - @JsonKey(name: 'insta_link') + @JsonKey(name: 'insta_link', includeIfNull: false) String? get instaLink; @override - @JsonKey(name: 'personal_site_link') + @JsonKey(name: 'personal_site_link', includeIfNull: false) String? get personalSiteLink; /// Create a copy of SocialLinksModel diff --git a/lib/features/consultant/become_consultant/models/consultant_profile_model.g.dart b/lib/features/consultant/become_consultant/models/consultant_profile_model.g.dart index ab86917a..e611c201 100644 --- a/lib/features/consultant/become_consultant/models/consultant_profile_model.g.dart +++ b/lib/features/consultant/become_consultant/models/consultant_profile_model.g.dart @@ -23,13 +23,21 @@ _$ConsultantProfileModelImpl _$$ConsultantProfileModelImplFromJson( ); Map _$$ConsultantProfileModelImplToJson( - _$ConsultantProfileModelImpl instance) => - { - 'has_medical_education': instance.hasMedicalEducation, - 'social_links': instance.socialLinks, - 'cert_photos': instance.photos, - 'completed_courses': instance.certificates, - }; + _$ConsultantProfileModelImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('has_medical_education', instance.hasMedicalEducation); + writeNotNull('social_links', instance.socialLinks); + writeNotNull('cert_photos', instance.photos); + writeNotNull('completed_courses', instance.certificates); + return val; +} _$SocialLinksModelImpl _$$SocialLinksModelImplFromJson( Map json) => @@ -42,14 +50,22 @@ _$SocialLinksModelImpl _$$SocialLinksModelImplFromJson( ); Map _$$SocialLinksModelImplToJson( - _$SocialLinksModelImpl instance) => - { - 'vk_link': instance.vkLink, - 'tg_link': instance.tgLink, - 'fb_link': instance.fbLink, - 'insta_link': instance.instaLink, - 'personal_site_link': instance.personalSiteLink, - }; + _$SocialLinksModelImpl instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('vk_link', instance.vkLink); + writeNotNull('tg_link', instance.tgLink); + writeNotNull('fb_link', instance.fbLink); + writeNotNull('insta_link', instance.instaLink); + writeNotNull('personal_site_link', instance.personalSiteLink); + return val; +} _$CertificatesModelImpl _$$CertificatesModelImplFromJson( Map json) => 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 b60cab68..78a76708 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 @@ -12,7 +12,10 @@ class RegisterConsultantCubit extends Cubit { required this.consultantRepository, }) : super( RegisterConsultantState( - consultant: ConsultantProfileModel(), + consultant: ConsultantProfileModel( + socialLinks: SocialLinksModel(), + hasMedicalEducation: true, + ), ), ); diff --git a/lib/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart index ac8b4e11..e51aff3a 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart @@ -1,6 +1,8 @@ +import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class AdditionalSettingsScreen extends StatefulWidget { const AdditionalSettingsScreen({super.key}); @@ -13,6 +15,18 @@ class _AdditionalSettingsScreenState extends State { bool publishDiplomasAndCertificatesInProfile = true; bool haveMedicalDegree = true; + @override + void initState() { + if (context.read().state.consultant.hasMedicalEducation != null) { + haveMedicalDegree = context + .read() + .state + .consultant + .hasMedicalEducation!; + } + super.initState(); + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -50,6 +64,11 @@ class _AdditionalSettingsScreenState extends State { setState(() { haveMedicalDegree = !haveMedicalDegree; }); + context.read().updateConsultantProfileModel( + context.read().state.consultant.copyWith( + hasMedicalEducation: haveMedicalDegree, + ), + ); }, ), ], diff --git a/lib/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart index 1eb412f7..27a4aa71 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart @@ -1,10 +1,12 @@ import 'package:eatvio_app/features/consultant/become_consultant/entities/diploma_entity.dart'; -import 'package:eatvio_app/theme/theme_helper.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_cubit.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class CertificatesAndDiplomasScreen extends StatefulWidget { const CertificatesAndDiplomasScreen({super.key}); @@ -24,6 +26,27 @@ class _CertificatesAndDiplomasScreenState extends State().state.consultant.certificates != null) { + diplomasList = context + .read() + .state + .consultant + .certificates! + .map( + (e) => DiplomaEntity( + date: DateTime.tryParse(e.date), + textEditingController: TextEditingController(text: e.title), + enableDeleteButton: true, + ), + ) + .toList(); + diplomasList.last.enableDeleteButton = false; + } + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -113,6 +136,7 @@ class _CertificatesAndDiplomasScreenState extends State( context: context, builder: (BuildContext context) => Container( @@ -166,34 +191,7 @@ class _CertificatesAndDiplomasScreenState extends State().updateConsultantProfileModel( + context.read().state.consultant.copyWith( + certificates: diplomasList.map((e) => CertificatesModel(title: e.textEditingController.text, date: e.date.toString())).toList().sublist( + 0, + diplomasList.length - 1, + ), + ), + ); } } 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 5f7abbef..51684b88 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 @@ -1,7 +1,9 @@ import 'dart:io'; +import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; class PhotoOrScansScreen extends StatefulWidget { @@ -12,7 +14,14 @@ class PhotoOrScansScreen extends StatefulWidget { } class _PhotoOrScansScreenState extends State { - List images = []; + List imagesPaths = []; + + @override + void initState() { + super.initState(); + imagesPaths = + context.read().state.consultant.photos != null ? [...context.read().state.consultant.photos!] : []; + } @override Widget build(BuildContext context) { @@ -42,15 +51,17 @@ class _PhotoOrScansScreenState extends State { mainAxisSpacing: 8, ), itemBuilder: (BuildContext context, int index) { - if (index == images.length) { + if (index == imagesPaths.length) { return InkWell( borderRadius: BorderRadius.circular(20), onTap: () async { final picker = ImagePicker(); List? newImages = await picker.pickMultiImage(); + List newImagesPaths = newImages.map((e) => e.path).toList(); setState(() { - images = [...images, ...newImages]; + imagesPaths = [...imagesPaths, ...newImagesPaths]; }); + _saveState(); }, child: Ink( decoration: BoxDecoration( @@ -78,7 +89,7 @@ class _PhotoOrScansScreenState extends State { image: DecorationImage( fit: BoxFit.fitHeight, image: FileImage( - File(images[index].path), + File(imagesPaths[index]), ), ), ), @@ -89,8 +100,9 @@ class _PhotoOrScansScreenState extends State { child: InkWell( onTap: () { setState(() { - images.removeAt(index); + imagesPaths.removeAt(index); }); + _saveState(); }, customBorder: CircleBorder(), child: Ink( @@ -112,10 +124,18 @@ class _PhotoOrScansScreenState extends State { ); } }, - itemCount: images.length + 1, + itemCount: imagesPaths.length + 1, ), ], ), ); } + + _saveState() { + context.read().updateConsultantProfileModel( + context.read().state.consultant.copyWith( + photos: imagesPaths, + ), + ); + } } diff --git a/lib/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart index 0de6e609..52e45d84 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart @@ -1,6 +1,9 @@ +import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mask_text_input_formatter/mask_text_input_formatter.dart'; class SocialNetworksScreen extends StatefulWidget { const SocialNetworksScreen({super.key}); @@ -10,6 +13,40 @@ class SocialNetworksScreen extends StatefulWidget { } class _SocialNetworksScreenState extends State { + final vkController = TextEditingController(); + final telegramController = TextEditingController(); + final webSiteController = TextEditingController(); + + var maskFormatter = new MaskTextInputFormatter( + mask: 'https://########################################', + filter: { + '#': RegExp(r"(\w+)"), + }, + type: MaskAutoCompletionType.lazy, + ); + var maskTgFormatter = new MaskTextInputFormatter( + mask: 'https://tg.me/########################################', + filter: { + '#': RegExp(r"(\w+)"), + }, + type: MaskAutoCompletionType.lazy, + ); + var maskVkFormatter = new MaskTextInputFormatter( + mask: 'https://vk.com/########################################', + filter: { + '#': RegExp(r"(\w+)"), + }, + type: MaskAutoCompletionType.lazy, + ); + + @override + void initState() { + super.initState(); + vkController.text = context.read().state.consultant.socialLinks!.vkLink ?? ''; + telegramController.text = context.read().state.consultant.socialLinks!.tgLink ?? ''; + webSiteController.text = context.read().state.consultant.socialLinks!.personalSiteLink ?? ''; + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -46,7 +83,12 @@ class _SocialNetworksScreenState extends State { SizedBox( width: MediaQuery.of(context).size.width - 92, child: CustomTextFormField( - hintText: 'ВКонтакте', + hintText: 'vk.com/', + controller: vkController, + inputFormatters: [maskVkFormatter], + onChanged: (value) { + setLinksState(vkLink: value); + }, ), ), ], @@ -71,7 +113,12 @@ class _SocialNetworksScreenState extends State { SizedBox( width: MediaQuery.of(context).size.width - 92, child: CustomTextFormField( - hintText: 'Telegram', + hintText: 'tg.me/', + inputFormatters: [maskTgFormatter], + controller: telegramController, + onChanged: (value) { + setLinksState(tgLink: value); + }, ), ), ], @@ -96,7 +143,12 @@ class _SocialNetworksScreenState extends State { SizedBox( width: MediaQuery.of(context).size.width - 92, child: CustomTextFormField( - hintText: 'Личный сайт', + hintText: 'https://', + inputFormatters: [maskFormatter], + controller: webSiteController, + onChanged: (value) { + setLinksState(personalSiteLink: value); + }, ), ), ], @@ -105,4 +157,32 @@ class _SocialNetworksScreenState extends State { ), ); } + + setLinksState({ + String? tgLink, + String? vkLink, + String? personalSiteLink, + }) { + context.read().updateConsultantProfileModel( + context.read().state.consultant.copyWith( + socialLinks: context.read().state.consultant.socialLinks?.copyWith( + tgLink: tgLink == null + ? context.read().state.consultant.socialLinks?.tgLink + : tgLink.isEmpty + ? null + : tgLink, + vkLink: vkLink == null + ? context.read().state.consultant.socialLinks?.vkLink + : vkLink.isEmpty + ? null + : vkLink, + personalSiteLink: personalSiteLink == null + ? context.read().state.consultant.socialLinks?.personalSiteLink + : personalSiteLink.isEmpty + ? null + : personalSiteLink, + ), + ), + ); + } } 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 bd2f3d0b..57c11b07 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,13 +1,16 @@ import 'package:eatvio_app/core/utils/image_constant.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/pages/additional_settings_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/last_step_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart'; +import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; class StepperBecomeConsultantScreen extends StatefulWidget { @@ -21,197 +24,202 @@ class _StepperBecomeConsultantScreenState extends State(); @override Widget build(BuildContext context) { - return Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Row( - children: [ - Container( - height: 48, - width: MediaQuery.of(context).size.width * 0.2, - padding: EdgeInsets.only(left: 16, right: 4), - child: FloatingActionButton.extended( - heroTag: 'backButtonRegistration', - highlightElevation: 0, - focusElevation: 0, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: Color(0xFFEAF2FD), - onPressed: () async { - setState(() { - pageController.previousPage( - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, - ); - }); - }, - label: Icon( - Icons.arrow_back_ios_new_rounded, - color: appTheme.blueA400, - ), - ), - ), - Container( - height: 48, - width: MediaQuery.of(context).size.width * 0.8, - padding: EdgeInsets.only(left: 4, right: 16), - child: FloatingActionButton.extended( - heroTag: 'nextButtonRegistration', - highlightElevation: 0, - elevation: 0, - focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: appTheme.blueA400, - onPressed: () async { - if (step == 6) { - await showModalBottomSheet( - useRootNavigator: true, - showDragHandle: true, - context: context, - builder: (context) => Container( - padding: EdgeInsets.all(16), - width: MediaQuery.of(context).size.width, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Спасибо!\nВаши данные отправлены на модерацию', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - height: 1, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: appTheme.blueA400, - shape: BoxShape.circle, - ), - child: Icon( - size: 32, - Icons.done, - color: Colors.white, - ), - ), - const SizedBox(height: 16), - Text( - 'В течении трех рабочих дней Вам придет уведомление о смене статуса', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - height: 1, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 16), - InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Ink( - width: MediaQuery.of(context).size.width - 32, - height: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), - child: Center( - child: Text( - 'Закрыть', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } else { + return BlocProvider( + create: (context) => registrationConsultantCubit, + child: Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Row( + children: [ + Container( + height: 48, + width: MediaQuery.of(context).size.width * 0.2, + padding: EdgeInsets.only(left: 16, right: 4), + child: FloatingActionButton.extended( + heroTag: 'backButtonRegistration', + highlightElevation: 0, + focusElevation: 0, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + backgroundColor: Color(0xFFEAF2FD), + onPressed: () async { setState(() { - pageController.nextPage( + pageController.previousPage( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, ); }); - } - }, - label: Text( - 'Продолжить', - style: theme.textTheme.titleSmall!.copyWith(color: Colors.white), + }, + label: Icon( + Icons.arrow_back_ios_new_rounded, + color: appTheme.blueA400, + ), ), ), - ), - ], - ), - appBar: AppBar( - automaticallyImplyLeading: false, - leading: Builder( - builder: (context) => IconButton( - icon: Icon(Icons.close_rounded), - onPressed: () => GoRouter.of(context).pop(), - ), - ), - centerTitle: true, - title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 16), - child: Text('шаг $step из 6'), - ), - ], - bottom: PreferredSize( - preferredSize: Size(MediaQuery.of(context).size.width, 32), - child: Padding( - padding: const EdgeInsets.all(16), - child: TweenAnimationBuilder( - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, - tween: Tween( - begin: 1 / 6, - end: progressValue, + Container( + height: 48, + width: MediaQuery.of(context).size.width * 0.8, + padding: EdgeInsets.only(left: 4, right: 16), + child: FloatingActionButton.extended( + heroTag: 'nextButtonRegistration', + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + backgroundColor: appTheme.blueA400, + onPressed: () async { + if (step == 6) { + await registrationConsultantCubit.registerConsultant(); + await showModalBottomSheet( + useRootNavigator: true, + showDragHandle: true, + context: context, + builder: (context) => Container( + padding: EdgeInsets.all(16), + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Спасибо!\nВаши данные отправлены на модерацию', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + height: 1, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: appTheme.blueA400, + shape: BoxShape.circle, + ), + child: Icon( + size: 32, + Icons.done, + color: Colors.white, + ), + ), + const SizedBox(height: 16), + Text( + 'В течении трех рабочих дней Вам придет уведомление о смене статуса', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + height: 1, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 16), + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Ink( + width: MediaQuery.of(context).size.width - 32, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: Center( + child: Text( + 'Закрыть', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } else { + setState(() { + pageController.nextPage( + duration: const Duration(milliseconds: 350), + curve: Curves.easeInOut, + ); + }); + } + }, + label: Text( + 'Продолжить', + style: theme.textTheme.titleSmall!.copyWith(color: Colors.white), + ), ), - builder: (context, value, _) => LinearProgressIndicator( - value: value, - borderRadius: BorderRadius.circular(16), - minHeight: 8, - backgroundColor: Colors.grey.shade100, - valueColor: AlwaysStoppedAnimation(appTheme.blueA400), + ), + ], + ), + appBar: AppBar( + automaticallyImplyLeading: false, + leading: Builder( + builder: (context) => IconButton( + icon: Icon(Icons.close_rounded), + onPressed: () => GoRouter.of(context).pop(), + ), + ), + centerTitle: true, + title: AppbarTitleImage(imagePath: ImageConstant.imgLogoEatvio), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 16), + child: Text('шаг $step из 6'), + ), + ], + bottom: PreferredSize( + preferredSize: Size(MediaQuery.of(context).size.width, 32), + child: Padding( + padding: const EdgeInsets.all(16), + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 350), + curve: Curves.easeInOut, + tween: Tween( + begin: 1 / 6, + end: progressValue, + ), + builder: (context, value, _) => LinearProgressIndicator( + value: value, + borderRadius: BorderRadius.circular(16), + minHeight: 8, + backgroundColor: Colors.grey.shade100, + valueColor: AlwaysStoppedAnimation(appTheme.blueA400), + ), ), ), ), ), - ), - body: PageView( - physics: NeverScrollableScrollPhysics(), - controller: pageController, - onPageChanged: (index) { - setState(() { - step = index + 1; - progressValue = step / 6; - }); - }, - children: [ - CertificatesAndDiplomasScreen(), - PhotoOrScansScreen(), - AdditionalSettingsScreen(), - SocialNetworksScreen(), - RegistrationAsScreen(), - LastStepScreen(), - ], + body: PageView( + physics: NeverScrollableScrollPhysics(), + controller: pageController, + onPageChanged: (index) { + setState(() { + step = index + 1; + progressValue = step / 6; + }); + }, + children: [ + CertificatesAndDiplomasScreen(), + PhotoOrScansScreen(), + AdditionalSettingsScreen(), + SocialNetworksScreen(), + RegistrationAsScreen(), + LastStepScreen(), + ], + ), ), ); } diff --git a/lib/features/consultant/become_consultant/repositories/consultant_repository.dart b/lib/features/consultant/become_consultant/repositories/consultant_repository.dart index 35594b96..f0ea7897 100644 --- a/lib/features/consultant/become_consultant/repositories/consultant_repository.dart +++ b/lib/features/consultant/become_consultant/repositories/consultant_repository.dart @@ -16,7 +16,7 @@ class ConsultantRepository { }) async { try { return await consultantService.consultantRegistration( - consultantModel: consultant.toJson(), + consultantModel: consultant, ); } catch (e) { print(e); diff --git a/lib/features/consultant/become_consultant/services/consultant_service.dart b/lib/features/consultant/become_consultant/services/consultant_service.dart index ecfe5e14..4479122f 100644 --- a/lib/features/consultant/become_consultant/services/consultant_service.dart +++ b/lib/features/consultant/become_consultant/services/consultant_service.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; import 'package:retrofit/retrofit.dart'; part 'consultant_service.g.dart'; @@ -9,7 +10,7 @@ abstract class ConsultantService { @POST('/api/consultant_request/registration') Future consultantRegistration({ - @Body() required Map consultantModel, + @Body() required ConsultantProfileModel consultantModel, }); @POST('/api/consultant_request/photos/upload') diff --git a/lib/features/consultant/become_consultant/services/consultant_service.g.dart b/lib/features/consultant/become_consultant/services/consultant_service.g.dart index 41bc384c..870c1d5d 100644 --- a/lib/features/consultant/become_consultant/services/consultant_service.g.dart +++ b/lib/features/consultant/become_consultant/services/consultant_service.g.dart @@ -23,12 +23,11 @@ class _ConsultantService implements ConsultantService { @override Future consultantRegistration( - {required Map consultantModel}) async { + {required ConsultantProfileModel consultantModel}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; - final _data = {}; - _data.addAll(consultantModel); + final _data = consultantModel; final _options = _setStreamType(Options( method: 'POST', headers: _headers, diff --git a/pubspec.yaml b/pubspec.yaml index de3084db..d87c49e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: audioplayers: ^6.1.0 intl: ^0.19.0 rive: ^0.13.14 + mask_text_input_formatter: ^2.9.0 dev_dependencies: build_runner: ^2.4.13