forked from eatvio/eatvio_app
feat(setting): added profile data apdating for sttings screen, added validation for general info form
This commit is contained in:
@@ -89,16 +89,7 @@ class MainNavigationRoutes {
|
||||
GoRoute(
|
||||
path: RouteConstants.menuScreen,
|
||||
name: 'menuScreen',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return MenuScreen(profileModel: state.profile);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: MenuScreen()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -7,7 +7,19 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||
|
||||
class MenuCubit extends Cubit<MenuState> {
|
||||
final ProfileRepository profileRepository;
|
||||
MenuCubit(this.profileRepository) : super(MenuState());
|
||||
MenuCubit(this.profileRepository) : super(const MenuState.initial());
|
||||
|
||||
Future<void> getMyProfile() async {
|
||||
emit(const MenuState.loading());
|
||||
try {
|
||||
final profile = await profileRepository.getMyProfile();
|
||||
emit(MenuState.loaded(profileModel: profile));
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
log(e.toString());
|
||||
emit(const MenuState.error());
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> makePassKey() async {
|
||||
try {
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
class MenuState {}
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'menu_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class MenuState with _$MenuState {
|
||||
const factory MenuState.initial() = MenuStateInitial;
|
||||
|
||||
const factory MenuState.loading() = MenuStateLoading;
|
||||
|
||||
const factory MenuState.loaded({
|
||||
required ProfileModel profileModel,
|
||||
}) = MenuStateLoaded;
|
||||
|
||||
const factory MenuState.error() = MenuStateError;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
@@ -13,9 +15,9 @@ import 'package:eatvio_app/features/consultant_rating/presentation/consultant_ra
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -25,90 +27,107 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class MenuScreen extends StatelessWidget {
|
||||
final ProfileModel profileModel;
|
||||
|
||||
const MenuScreen({super.key, required this.profileModel});
|
||||
const MenuScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<MenuCubit>(),
|
||||
create: (context) => sl<MenuCubit>()..getMyProfile(),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(centerTitle: false, title: const Text('Меню')),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
|
||||
if (!hidePlans) ...[
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/finance.svg',
|
||||
title: 'Мои финансы',
|
||||
onTap: () {
|
||||
if (profileModel.roles.contains('consultant')) {
|
||||
GoRouter.of(context).push(ConsultantFinancesScreen.path);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).push(UserPurchasesScreen.path);
|
||||
},
|
||||
return BlocBuilder<MenuCubit, MenuState>(
|
||||
builder: (BuildContext context, MenuState state) {
|
||||
if (state is MenuStateLoaded) {
|
||||
final profileModel = state.profileModel;
|
||||
return Scaffold(
|
||||
appBar: AppBar(centerTitle: false, title: const Text('Меню')),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
|
||||
if (!hidePlans) ...[
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/finance.svg',
|
||||
title: 'Мои финансы',
|
||||
onTap: () {
|
||||
if (profileModel.roles.contains('consultant')) {
|
||||
GoRouter.of(context).push(ConsultantFinancesScreen.path);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).push(UserPurchasesScreen.path);
|
||||
},
|
||||
),
|
||||
],
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(
|
||||
leading: 'assets/icons/consultant_rating_icon.svg',
|
||||
title: 'Мой рейтинг',
|
||||
onTap: () {
|
||||
GoRouter.of(context).push(ConsultantRatingScreen.path);
|
||||
},
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: ImageConstant.icnGlossary,
|
||||
title: 'Глоссарий',
|
||||
onTap: () async {
|
||||
final passKey = await context.read<MenuCubit>().makePassKey();
|
||||
if (passKey != null && context.mounted) {
|
||||
GoRouter.of(context).push('/glossary-screen', extra: passKey);
|
||||
}
|
||||
},
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/notifications.svg',
|
||||
title: 'Уведомления',
|
||||
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
||||
// _divider(context),
|
||||
// _tile(
|
||||
// leading: ImageConstant.icnDevTools,
|
||||
// title: 'Для разработчиков',
|
||||
// onTap: () {
|
||||
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||
// },
|
||||
// ),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||
const SizedBox(height: 10),
|
||||
const AppInfo(),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(
|
||||
leading: 'assets/icons/consultant_rating_icon.svg',
|
||||
title: 'Мой рейтинг',
|
||||
onTap: () {
|
||||
GoRouter.of(context).push(ConsultantRatingScreen.path);
|
||||
},
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: ImageConstant.icnGlossary,
|
||||
title: 'Глоссарий',
|
||||
onTap: () async {
|
||||
final passKey = await context.read<MenuCubit>().makePassKey();
|
||||
if (passKey != null && context.mounted) {
|
||||
GoRouter.of(context).push('/glossary-screen', extra: passKey);
|
||||
}
|
||||
},
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/notifications.svg',
|
||||
title: 'Уведомления',
|
||||
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
||||
// _divider(context),
|
||||
// _tile(
|
||||
// leading: ImageConstant.icnDevTools,
|
||||
// title: 'Для разработчиков',
|
||||
// onTap: () {
|
||||
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||
// },
|
||||
// ),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||
const SizedBox(height: 10),
|
||||
const AppInfo(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is MenuStateError) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(centerTitle: false, title: const Text('Меню')),
|
||||
body: DataErrorWidget(onReload: () async => context.read<MenuCubit>().getMyProfile()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(centerTitle: false, title: const Text('Меню')),
|
||||
body: const Loader(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -8,15 +10,11 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class GeneralInfoScreen extends StatefulWidget {
|
||||
final SettingsEntity settingsEntity;
|
||||
|
||||
const GeneralInfoScreen({
|
||||
super.key,
|
||||
required this.settingsEntity,
|
||||
});
|
||||
const GeneralInfoScreen({super.key, required this.settingsEntity});
|
||||
|
||||
@override
|
||||
State<GeneralInfoScreen> createState() => _GeneralInfoScreenState();
|
||||
@@ -31,6 +29,10 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
final cityController = TextEditingController();
|
||||
final languageController = TextEditingController();
|
||||
late SelectionPopupModel isPublicProfile;
|
||||
bool isLoading = false;
|
||||
bool isNameMissing = false;
|
||||
bool isLastNameMissing = false;
|
||||
bool isLanguageMissing = false;
|
||||
|
||||
final List<SelectionPopupModel> genderSelectionPopupList = [
|
||||
const SelectionPopupModel(id: 0, title: 'Женский', value: 'female'),
|
||||
@@ -53,8 +55,9 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
countryController.text = widget.settingsEntity.profileDetailsModel.country;
|
||||
cityController.text = widget.settingsEntity.profileDetailsModel.city;
|
||||
languageController.text = widget.settingsEntity.profileDetailsModel.lang;
|
||||
isPublicProfile =
|
||||
widget.settingsEntity.profileSettingsModel.isPublicProfile == true ? isPublicProfileSelectionPopupList[1] : isPublicProfileSelectionPopupList[0];
|
||||
isPublicProfile = widget.settingsEntity.profileSettingsModel.isPublicProfile == true
|
||||
? isPublicProfileSelectionPopupList[1]
|
||||
: isPublicProfileSelectionPopupList[0];
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -62,9 +65,7 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
final s = S.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Общая информация'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Общая информация')),
|
||||
bottomSheet: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
@@ -80,34 +81,31 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
children: [
|
||||
Text(s.first_name),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: firstNameController,
|
||||
hintText: 'Введите имя',
|
||||
onChanged: (newValue) {},
|
||||
Column(
|
||||
children: [
|
||||
CustomTextFormField(hasError: isNameMissing, controller: firstNameController, hintText: 'Введите имя', onChanged: (newValue) {}),
|
||||
if (isNameMissing) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Имя не может быть пустым')],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(s.last_name),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: lastNameController,
|
||||
hintText: 'Введите фамилию',
|
||||
onChanged: (newValue) {},
|
||||
Column(
|
||||
children: [
|
||||
CustomTextFormField(hasError: isLastNameMissing, controller: lastNameController, hintText: 'Введите фамилию', onChanged: (newValue) {}),
|
||||
if (isLastNameMissing) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Фамилия не может быть пустой')],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(s.date_of_birth),
|
||||
const SizedBox(height: 4),
|
||||
ListTile(
|
||||
onTap: onTapCalendar(context),
|
||||
trailing: Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
trailing: Icon(Icons.keyboard_arrow_down_rounded, color: Colors.grey.shade700),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
title: Text(selectedDate.format('dd MMMM yyyy', 'ru')),
|
||||
tileColor: const Color(0xFFF6F6F6),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Пол'),
|
||||
@@ -124,20 +122,19 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
const SizedBox(height: 16),
|
||||
const Text('Страна'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: countryController,
|
||||
),
|
||||
CustomTextFormField(controller: countryController),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Город'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: cityController,
|
||||
),
|
||||
CustomTextFormField(controller: cityController),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Предпочтительный язык общения'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: languageController,
|
||||
Column(
|
||||
children: [
|
||||
CustomTextFormField(hasError: isLanguageMissing, controller: languageController),
|
||||
if (isLanguageMissing) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Язык не может быть пустым')],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Показывать анкету'),
|
||||
@@ -181,59 +178,92 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
|
||||
builder: (BuildContext context) => Container(
|
||||
height: 216,
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
margin: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: child,
|
||||
),
|
||||
child: SafeArea(top: false, child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InkWell _saveButton() {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
widget.settingsEntity.settingsCubit.updateProfileDetails(
|
||||
profileDetailsModel: (widget.settingsEntity.settingsCubit.state as SettingsStateLoaded).profileDetails.copyWith(
|
||||
firstName: firstNameController.text,
|
||||
lastName: lastNameController.text,
|
||||
birthdate: selectedDate.toString(),
|
||||
gender: gender.value,
|
||||
country: countryController.text,
|
||||
city: cityController.text,
|
||||
lang: languageController.text,
|
||||
),
|
||||
);
|
||||
widget.settingsEntity.settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: (widget.settingsEntity.settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith(
|
||||
isPublicProfile: isPublicProfile.value,
|
||||
bool _checkFields() {
|
||||
bool hasError = false;
|
||||
if (firstNameController.text.isEmpty) {
|
||||
setState(() {
|
||||
isNameMissing = true;
|
||||
});
|
||||
hasError = true;
|
||||
} else {
|
||||
setState(() {
|
||||
isNameMissing = false;
|
||||
});
|
||||
}
|
||||
if (lastNameController.text.isEmpty) {
|
||||
setState(() {
|
||||
isLastNameMissing = true;
|
||||
});
|
||||
hasError = true;
|
||||
} else {
|
||||
setState(() {
|
||||
isLastNameMissing = false;
|
||||
});
|
||||
}
|
||||
if (languageController.text.isEmpty) {
|
||||
setState(() {
|
||||
isLanguageMissing = true;
|
||||
});
|
||||
hasError = true;
|
||||
} else {
|
||||
setState(() {
|
||||
isLanguageMissing = false;
|
||||
});
|
||||
}
|
||||
return hasError;
|
||||
}
|
||||
|
||||
Widget _saveButton() {
|
||||
return isLoading
|
||||
? const Center(child: Loader())
|
||||
: InkWell(
|
||||
onTap: () async {
|
||||
final hasError = _checkFields();
|
||||
if (hasError) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
await widget.settingsEntity.settingsCubit.updateProfileDetails(
|
||||
profileDetailsModel: (widget.settingsEntity.settingsCubit.state as SettingsStateLoaded).profileDetails.copyWith(
|
||||
firstName: firstNameController.text,
|
||||
lastName: lastNameController.text,
|
||||
birthdate: selectedDate.toString(),
|
||||
gender: gender.value,
|
||||
country: countryController.text,
|
||||
city: cityController.text,
|
||||
lang: languageController.text,
|
||||
),
|
||||
);
|
||||
await widget.settingsEntity.settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: (widget.settingsEntity.settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith(
|
||||
isPublicProfile: isPublicProfile.value,
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Сохранить',
|
||||
style: TextStyle(color: Colors.white, height: 1, fontWeight: FontWeight.w600, fontSize: 18),
|
||||
),
|
||||
),
|
||||
);
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Сохранить',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,64 @@
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('MenuState', () {
|
||||
test('creates MenuState instance', () {
|
||||
final state = MenuState();
|
||||
test('creates MenuState initial state', () {
|
||||
const state = MenuState.initial();
|
||||
expect(state, isNotNull);
|
||||
expect(state, isA<MenuState>());
|
||||
expect(state, isA<MenuStateInitial>());
|
||||
});
|
||||
|
||||
test('creates multiple MenuState instances', () {
|
||||
final state1 = MenuState();
|
||||
final state2 = MenuState();
|
||||
|
||||
expect(state1, isNotNull);
|
||||
expect(state2, isNotNull);
|
||||
expect(state1, isA<MenuState>());
|
||||
expect(state2, isA<MenuState>());
|
||||
test('creates MenuState loading state', () {
|
||||
const state = MenuState.loading();
|
||||
expect(state, isNotNull);
|
||||
expect(state, isA<MenuStateLoading>());
|
||||
});
|
||||
|
||||
test('MenuState instances are not equal by default', () {
|
||||
final state1 = MenuState();
|
||||
final state2 = MenuState();
|
||||
test('creates MenuState error state', () {
|
||||
const state = MenuState.error();
|
||||
expect(state, isNotNull);
|
||||
expect(state, isA<MenuStateError>());
|
||||
});
|
||||
|
||||
// MenuState is a regular class without overridden == operator,
|
||||
// so instances are compared by reference
|
||||
expect(state1 == state2, isFalse);
|
||||
expect(state1, isNot(same(state2)));
|
||||
test('creates MenuState loaded state', () {
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: 'https://example.com/icon.jpg',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
slug: 'test-user',
|
||||
);
|
||||
const state = MenuState.loaded(profileModel: profile);
|
||||
expect(state, isNotNull);
|
||||
expect(state, isA<MenuStateLoaded>());
|
||||
expect((state as MenuStateLoaded).profileModel, isNotNull);
|
||||
expect((state).profileModel.id, 1);
|
||||
expect((state).profileModel.name, 'Test');
|
||||
expect((state).profileModel.email, 'test@example.com');
|
||||
expect((state).profileModel.phoneConfirmationState, 'confirmed');
|
||||
expect((state).profileModel.isPublicProfile, true);
|
||||
expect((state).profileModel.isBlockedByMe, false);
|
||||
expect((state).profileModel.isBlockedByAuthor, false);
|
||||
expect((state).profileModel.isBlockedByUser, false);
|
||||
expect((state).profileModel.isEntitySubscribedByMe, false);
|
||||
expect((state).profileModel.profilePhotoUrl, 'https://example.com/icon.jpg');
|
||||
expect((state).profileModel.roles, []);
|
||||
expect((state).profileModel.badges, []);
|
||||
expect((state).profileModel.subscriptions, []);
|
||||
expect((state).profileModel.subscribers, []);
|
||||
expect((state).profileModel.slug, 'test-user');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,11 +141,12 @@ void main() {
|
||||
final cubit = MenuCubit(repository);
|
||||
|
||||
when(repository.getPassKey(routeName: anyNamed('routeName'), routeParams: anyNamed('routeParams'))).thenAnswer((_) async => 'test-passkey');
|
||||
when(repository.getMyProfile()).thenAnswer((_) async => mockUserProfile);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repository);
|
||||
sl.registerLazySingleton<MenuCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MenuScreen(profileModel: mockUserProfile)));
|
||||
await tester.pumpWidget(wrap(const MenuScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Меню'), findsOneWidget);
|
||||
@@ -157,11 +158,12 @@ void main() {
|
||||
final cubit = MenuCubit(repository);
|
||||
|
||||
when(repository.getPassKey(routeName: anyNamed('routeName'), routeParams: anyNamed('routeParams'))).thenAnswer((_) async => 'test-passkey');
|
||||
when(repository.getMyProfile()).thenAnswer((_) async => mockConsultantProfile);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repository);
|
||||
sl.registerLazySingleton<MenuCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MenuScreen(profileModel: mockConsultantProfile)));
|
||||
await tester.pumpWidget(wrap(const MenuScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Меню'), findsOneWidget);
|
||||
|
||||
Reference in New Issue
Block a user