feature: add consultant

This commit is contained in:
mso7nueh
2024-10-11 17:40:00 +03:00
parent 720d75e6ff
commit 1462334155
14 changed files with 480 additions and 310 deletions

View File

@@ -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

View File

@@ -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<String>? photos,
@JsonKey(name: 'completed_courses') List<CertificatesModel>? 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<String>? photos,
@JsonKey(name: 'completed_courses', includeIfNull: false) List<CertificatesModel>? certificates,
}) = _ConsultantProfileModel;
factory ConsultantProfileModel.fromJson(Map<String, Object?> 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<String, Object?> json) => _$SocialLinksModelFromJson(json);

View File

@@ -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<String>? get photos => throw _privateConstructorUsedError;
@JsonKey(name: 'completed_courses')
@JsonKey(name: 'completed_courses', includeIfNull: false)
List<CertificatesModel>? 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<String>? 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<String>? photos,
@JsonKey(name: 'completed_courses', includeIfNull: false)
List<CertificatesModel>? 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<String>? 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<String>? photos,
@JsonKey(name: 'completed_courses', includeIfNull: false)
List<CertificatesModel>? 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<String>? 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<String>? photos,
@JsonKey(name: 'completed_courses', includeIfNull: false)
final List<CertificatesModel>? 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<String>? _photos;
@override
@JsonKey(name: 'cert_photos')
@JsonKey(name: 'cert_photos', includeIfNull: false)
List<String>? get photos {
final value = _photos;
if (value == null) return null;
@@ -208,7 +214,7 @@ class _$ConsultantProfileModelImpl implements _ConsultantProfileModel {
final List<CertificatesModel>? _certificates;
@override
@JsonKey(name: 'completed_courses')
@JsonKey(name: 'completed_courses', includeIfNull: false)
List<CertificatesModel>? 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<String>? photos,
@JsonKey(name: 'completed_courses')
final List<CertificatesModel>?
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<String>? photos,
@JsonKey(name: 'completed_courses', includeIfNull: false)
final List<CertificatesModel>? certificates}) =
_$ConsultantProfileModelImpl;
factory _ConsultantProfileModel.fromJson(Map<String, dynamic> 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<String>? get photos;
@override
@JsonKey(name: 'completed_courses')
@JsonKey(name: 'completed_courses', includeIfNull: false)
List<CertificatesModel>? get certificates;
/// Create a copy of ConsultantProfileModel
@@ -301,15 +310,15 @@ SocialLinksModel _$SocialLinksModelFromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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

View File

@@ -23,13 +23,21 @@ _$ConsultantProfileModelImpl _$$ConsultantProfileModelImplFromJson(
);
Map<String, dynamic> _$$ConsultantProfileModelImplToJson(
_$ConsultantProfileModelImpl instance) =>
<String, dynamic>{
'has_medical_education': instance.hasMedicalEducation,
'social_links': instance.socialLinks,
'cert_photos': instance.photos,
'completed_courses': instance.certificates,
};
_$ConsultantProfileModelImpl instance) {
final val = <String, dynamic>{};
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<String, dynamic> json) =>
@@ -42,14 +50,22 @@ _$SocialLinksModelImpl _$$SocialLinksModelImplFromJson(
);
Map<String, dynamic> _$$SocialLinksModelImplToJson(
_$SocialLinksModelImpl instance) =>
<String, dynamic>{
'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 = <String, dynamic>{};
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<String, dynamic> json) =>

View File

@@ -12,7 +12,10 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
required this.consultantRepository,
}) : super(
RegisterConsultantState(
consultant: ConsultantProfileModel(),
consultant: ConsultantProfileModel(
socialLinks: SocialLinksModel(),
hasMedicalEducation: true,
),
),
);

View File

@@ -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<AdditionalSettingsScreen> {
bool publishDiplomasAndCertificatesInProfile = true;
bool haveMedicalDegree = true;
@override
void initState() {
if (context.read<RegisterConsultantCubit>().state.consultant.hasMedicalEducation != null) {
haveMedicalDegree = context
.read<RegisterConsultantCubit>()
.state
.consultant
.hasMedicalEducation!;
}
super.initState();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -50,6 +64,11 @@ class _AdditionalSettingsScreenState extends State<AdditionalSettingsScreen> {
setState(() {
haveMedicalDegree = !haveMedicalDegree;
});
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
hasMedicalEducation: haveMedicalDegree,
),
);
},
),
],

View File

@@ -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<CertificatesAndDiplomasS
),
];
@override
void initState() {
super.initState();
if (context.read<RegisterConsultantCubit>().state.consultant.certificates != null) {
diplomasList = context
.read<RegisterConsultantCubit>()
.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<CertificatesAndDiplomasS
onTap: () {
setState(() {
diplomasList.remove(diploma);
_saveState();
});
},
borderRadius: BorderRadius.circular(20),
@@ -155,6 +179,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
}
void _showDialog(Widget child) {
FocusManager.instance.primaryFocus?.unfocus();
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
@@ -166,34 +191,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: Column(
children: [
child,
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,
),
),
),
),
),
],
),
child: child,
),
),
);
@@ -226,5 +224,17 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
diplomasList[diplomasList.indexOf(diploma)].enableDeleteButton = true;
});
}
_saveState();
}
_saveState() {
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
certificates: diplomasList.map((e) => CertificatesModel(title: e.textEditingController.text, date: e.date.toString())).toList().sublist(
0,
diplomasList.length - 1,
),
),
);
}
}

View File

@@ -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<PhotoOrScansScreen> {
List<XFile> images = [];
List<String> imagesPaths = [];
@override
void initState() {
super.initState();
imagesPaths =
context.read<RegisterConsultantCubit>().state.consultant.photos != null ? [...context.read<RegisterConsultantCubit>().state.consultant.photos!] : [];
}
@override
Widget build(BuildContext context) {
@@ -42,15 +51,17 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
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<XFile>? newImages = await picker.pickMultiImage();
List<String> 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<PhotoOrScansScreen> {
image: DecorationImage(
fit: BoxFit.fitHeight,
image: FileImage(
File(images[index].path),
File(imagesPaths[index]),
),
),
),
@@ -89,8 +100,9 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
child: InkWell(
onTap: () {
setState(() {
images.removeAt(index);
imagesPaths.removeAt(index);
});
_saveState();
},
customBorder: CircleBorder(),
child: Ink(
@@ -112,10 +124,18 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
);
}
},
itemCount: images.length + 1,
itemCount: imagesPaths.length + 1,
),
],
),
);
}
_saveState() {
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
photos: imagesPaths,
),
);
}
}

View File

@@ -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<SocialNetworksScreen> {
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<RegisterConsultantCubit>().state.consultant.socialLinks!.vkLink ?? '';
telegramController.text = context.read<RegisterConsultantCubit>().state.consultant.socialLinks!.tgLink ?? '';
webSiteController.text = context.read<RegisterConsultantCubit>().state.consultant.socialLinks!.personalSiteLink ?? '';
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -46,7 +83,12 @@ class _SocialNetworksScreenState extends State<SocialNetworksScreen> {
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<SocialNetworksScreen> {
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<SocialNetworksScreen> {
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<SocialNetworksScreen> {
),
);
}
setLinksState({
String? tgLink,
String? vkLink,
String? personalSiteLink,
}) {
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
socialLinks: context.read<RegisterConsultantCubit>().state.consultant.socialLinks?.copyWith(
tgLink: tgLink == null
? context.read<RegisterConsultantCubit>().state.consultant.socialLinks?.tgLink
: tgLink.isEmpty
? null
: tgLink,
vkLink: vkLink == null
? context.read<RegisterConsultantCubit>().state.consultant.socialLinks?.vkLink
: vkLink.isEmpty
? null
: vkLink,
personalSiteLink: personalSiteLink == null
? context.read<RegisterConsultantCubit>().state.consultant.socialLinks?.personalSiteLink
: personalSiteLink.isEmpty
? null
: personalSiteLink,
),
),
);
}
}

View File

@@ -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<StepperBecomeConsultantS
double progressValue = 1 / 6;
int step = 1;
final pageController = PageController();
final registrationConsultantCubit = sl<RegisterConsultantCubit>();
@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<RegisterConsultantCubit>(
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<double>(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
tween: Tween<double>(
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<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<double>(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
tween: Tween<double>(
begin: 1 / 6,
end: progressValue,
),
builder: (context, value, _) => LinearProgressIndicator(
value: value,
borderRadius: BorderRadius.circular(16),
minHeight: 8,
backgroundColor: Colors.grey.shade100,
valueColor: AlwaysStoppedAnimation<Color>(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(),
],
),
),
);
}

View File

@@ -16,7 +16,7 @@ class ConsultantRepository {
}) async {
try {
return await consultantService.consultantRegistration(
consultantModel: consultant.toJson(),
consultantModel: consultant,
);
} catch (e) {
print(e);

View File

@@ -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<void> consultantRegistration({
@Body() required Map<String, dynamic> consultantModel,
@Body() required ConsultantProfileModel consultantModel,
});
@POST('/api/consultant_request/photos/upload')

View File

@@ -23,12 +23,11 @@ class _ConsultantService implements ConsultantService {
@override
Future<void> consultantRegistration(
{required Map<String, dynamic> consultantModel}) async {
{required ConsultantProfileModel consultantModel}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(consultantModel);
final _data = consultantModel;
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,

View File

@@ -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