diff --git a/assets/icons/error_icon.svg b/assets/icons/error_icon.svg
new file mode 100644
index 00000000..2a0fd478
--- /dev/null
+++ b/assets/icons/error_icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/ok.svg b/assets/icons/ok.svg
new file mode 100644
index 00000000..a8f5b0a6
--- /dev/null
+++ b/assets/icons/ok.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 009c3959..899804f1 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -112,9 +112,14 @@ class AppRouter {
GoRoute(
path: '/about-plan-screen',
name: 'aboutPlanScreen',
- builder: (context, state) => AboutPlanScreen(
- planModel: state.extra as PlanModel,
- ),
+ builder: (context, state) {
+ final data = state.extra as Map;
+ return AboutPlanScreen(
+ plan: data['plan'] as PlanModel,
+ // index: data['index'] as int,
+ navigateToComments: data['navigateToComments'] as bool,
+ );
+ },
),
GoRoute(
path: '/about-recipes-screen',
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index 63bf1fed..10d91e99 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -30,12 +30,16 @@ import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.da
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
+import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
+import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
+import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
+import 'package:eatvio_app/features/plans/services/plan_service.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
@@ -78,9 +82,10 @@ Future init() async {
sl.registerFactory(() => AboutDietCubit(dietsRepository: sl()));
sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl()));
sl.registerFactory(() => SubCubit(profileRepository: sl()));
- sl.registerFactory(() => CreateDietCubit( profileRepository: sl(),createDietRepository: sl(), glossaryRepository: sl()));
+ sl.registerFactory(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
sl.registerFactory(() => CreatePlanCubit());
-
+ sl.registerFactory(() => FeedProfileCubit(profileRepository: sl()));
+ sl.registerFactory(() => AboutPlanCubit(planRepository: sl()));
// Repository
sl.registerLazySingleton(() => ChatRepository(chatService: sl()));
@@ -96,6 +101,7 @@ Future init() async {
sl.registerLazySingleton(() => DietRepository(dietsService: sl()));
sl.registerLazySingleton(() => BlogPostRepository(blogPostService: sl()));
sl.registerLazySingleton(() => CreateDietRepository(createDietService: sl()));
+ sl.registerLazySingleton(() => PlanRepository(planService: sl()));
// Services
sl.registerLazySingleton(() => ChatService(sl()));
@@ -111,6 +117,7 @@ Future init() async {
sl.registerLazySingleton(() => DietService(sl()));
sl.registerLazySingleton(() => BlogPostService(sl()));
sl.registerLazySingleton(() => CreateDietService(sl()));
+ sl.registerLazySingleton(() => PlanService(sl()));
// External
sl.registerLazySingleton(
diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart
index 77f7595e..ab7d7377 100644
--- a/lib/core/theme/theme_helper.dart
+++ b/lib/core/theme/theme_helper.dart
@@ -178,10 +178,14 @@ class PrimaryColors {
Color get transparentBlue => Color(0xFF2F80ED).withOpacity(0.1);
Color get lightGray0 => Color(0xFFF6F6F6);
-
+
Color get lightGray00 => Color(0xFFE9E9EA);
Color get dottedBorder => Color(0xFFE6E6E6);
+
+ Color get red => Color(0xFFCD4141);
+
+ Color get transparentBlack => Color(0xFF1313264).withOpacity(0.3);
}
PrimaryColors get appTheme => ThemeHelper().themeColor();
diff --git a/lib/core/utils/string_extentions.dart b/lib/core/utils/string_extentions.dart
index 4d0e5173..f474175e 100644
--- a/lib/core/utils/string_extentions.dart
+++ b/lib/core/utils/string_extentions.dart
@@ -1,8 +1,4 @@
-
-
-
extension Capitalization on String {
-
String capitalize() {
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
diff --git a/lib/core/widgets/animated_option_button.dart b/lib/core/widgets/animated_option_button.dart
new file mode 100644
index 00000000..f196c042
--- /dev/null
+++ b/lib/core/widgets/animated_option_button.dart
@@ -0,0 +1,34 @@
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+
+class AnimatedOptionButton extends StatelessWidget {
+ const AnimatedOptionButton({required this.text, required this.isSelected, required this.onTap});
+ final String text;
+ final bool isSelected;
+ final void Function() onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: onTap,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ height: 40,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(100),
+ color: isSelected ? appTheme.blueA400 : appTheme.whiteA700,
+ ),
+ child: Center(
+ child: AnimatedDefaultTextStyle(
+ duration: const Duration(milliseconds: 300),
+ style: TextStyles.textMSemibold.copyWith(
+ color: isSelected ? appTheme.whiteA700 : appTheme.black,
+ ),
+ child: Text(text),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/core/widgets/custom_drop_down_button.dart b/lib/core/widgets/custom_drop_down_button.dart
index 843d6730..714382e7 100644
--- a/lib/core/widgets/custom_drop_down_button.dart
+++ b/lib/core/widgets/custom_drop_down_button.dart
@@ -9,11 +9,13 @@ class CustomDropDownButton extends StatelessWidget {
required this.onChanged,
this.value,
this.hint,
+ this.hasError = false,
});
final T? value;
final List> items;
final void Function(T?)? onChanged;
final Widget? hint;
+ final bool hasError;
@override
Widget build(BuildContext context) {
return DropdownButtonFormField(
@@ -28,7 +30,15 @@ class CustomDropDownButton extends StatelessWidget {
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(20)),
- borderSide: BorderSide.none,
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
),
fillColor: appTheme.lightGray0,
filled: true,
diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart
index 34e1c7d7..b9588f68 100644
--- a/lib/core/widgets/custom_text_form_field.dart
+++ b/lib/core/widgets/custom_text_form_field.dart
@@ -39,6 +39,7 @@ class CustomTextFormField extends StatelessWidget {
this.textAlignVertical,
this.counter = const SizedBox.shrink(),
super.key,
+ this.hasError = false,
});
final Widget? counter;
final Alignment? alignment;
@@ -74,12 +75,12 @@ class CustomTextFormField extends StatelessWidget {
final int? maxLength;
final bool expands;
final TextAlignVertical? textAlignVertical;
+ final bool hasError;
@override
Widget build(BuildContext context) {
return Align(
alignment: alignment ?? Alignment.center,
- child: TextFormField(
-
+ child: TextFormField(
textAlignVertical: textAlignVertical,
expands: expands,
minLines: minLines,
@@ -123,17 +124,17 @@ class CustomTextFormField extends StatelessWidget {
border: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
- borderSide: BorderSide.none,
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
),
enabledBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
- borderSide: BorderSide.none,
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
),
focusedBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
- borderSide: BorderSide.none,
+ borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none,
),
),
keyboardType: keyboardType,
diff --git a/lib/core/widgets/diet_info_row.dart b/lib/core/widgets/diet_info_row.dart
index 06c910bb..fdf5f34a 100644
--- a/lib/core/widgets/diet_info_row.dart
+++ b/lib/core/widgets/diet_info_row.dart
@@ -3,19 +3,29 @@ import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
-class DietInfoRow extends StatelessWidget {
- const DietInfoRow({super.key, required this.diet, this.itemsBackgroundColor, this.disableDescriptionOfTypes = false});
+class InfoRow extends StatelessWidget {
+ const InfoRow({
+ super.key,
+ this.diet,
+ this.itemsBackgroundColor,
+ this.disableDescriptionOfTypes = false,
+ this.plan,
+ });
final Color? itemsBackgroundColor;
- final Diet diet;
+ final Diet? diet;
+ final PlanModel? plan;
+
final bool disableDescriptionOfTypes;
@override
Widget build(BuildContext context) {
final s = S.of(context);
+
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
@@ -31,7 +41,7 @@ class DietInfoRow extends StatelessWidget {
),
SizedBox(width: 4),
Text(
- diet.duration.toString() + ' ' + getDayForm(diet.duration, s),
+ diet == null ? '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}' : '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
@@ -53,12 +63,17 @@ class DietInfoRow extends StatelessWidget {
],
),
SizedBox(width: 12),
- InfoBouble(
- backgroundColor: itemsBackgroundColor,
- children: [
- TypeBadges(type: diet.type, disableDescriptionOfTypes: disableDescriptionOfTypes),
- ],
- ),
+ if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
+ InfoBouble(
+ backgroundColor: itemsBackgroundColor,
+ children: [
+ TypeBadges(
+ type: diet?.type,
+ planType: plan?.type,
+ disableDescriptionOfTypes: disableDescriptionOfTypes,
+ ),
+ ],
+ ),
],
),
);
diff --git a/lib/core/widgets/main_background_bouble.dart b/lib/core/widgets/main_background_bouble.dart
index 46e13f09..a901ecad 100644
--- a/lib/core/widgets/main_background_bouble.dart
+++ b/lib/core/widgets/main_background_bouble.dart
@@ -2,31 +2,41 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
class MainBackgroundBouble extends StatelessWidget {
- const MainBackgroundBouble(
- {super.key, required this.child, this.width, this.padding, this.enableShadow = false,});
+ const MainBackgroundBouble({
+ super.key,
+ required this.child,
+ this.width,
+ this.padding,
+ this.enableShadow = false,
+ this.hasError = false,
+ });
final double? width;
final Widget child;
final bool enableShadow;
+ final bool hasError;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
- boxShadow: enableShadow
- ? [
- BoxShadow(
- color: Color(0x00000000).withOpacity(0.2),
- spreadRadius: 0,
- blurRadius: 32,
- offset: Offset(0, 0),
- ),
- ]
- : null,),
+ border: hasError ? Border.all(color: appTheme.red, width: 1) : null,
+ boxShadow: enableShadow
+ ? [
+ BoxShadow(
+ color: Color(0x00000000).withOpacity(0.2),
+ spreadRadius: 0,
+ blurRadius: 32,
+ offset: Offset(0, 0),
+ ),
+ ]
+ : null,
+ ),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
color: appTheme.whiteA700,
+ border: hasError ? Border.all(color: appTheme.red, width: 1) : null,
),
width: width ?? MediaQuery.sizeOf(context).width,
padding: padding,
diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart
index ed4fd233..a4425e10 100644
--- a/lib/features/create_diet/presentation/create_diet_screen.dart
+++ b/lib/features/create_diet/presentation/create_diet_screen.dart
@@ -1,7 +1,10 @@
+import 'dart:ui';
+
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
+import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
@@ -11,6 +14,7 @@ import 'package:eatvio_app/features/create_diet/presentation/widgets/day_widget.
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_picker_widget.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_type_choice_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -38,63 +42,96 @@ class CreateDietScreenContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
- return Scaffold(
- backgroundColor: appTheme.gray100,
- appBar: AppBar(
- centerTitle: true,
- title: Text(
- s.create_diet,
- textAlign: TextAlign.center,
- overflow: TextOverflow.ellipsis,
- softWrap: true,
- style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
- ),
- ),
- body: SingleChildScrollView(
- child: Column(
- children: [
- SizedBox(height: 8),
- MainBackgroundBouble(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Column(
- children: [
- SizedBox(height: 16),
- CustomTextFormField(
- hintText: s.enter_diet_name,
- onChanged: (value) {
- context.read().setName(value);
- },
- ),
- SizedBox(height: 12),
- SizedBox(
- height: 132,
- child: CustomTextFormField(
- textAlignVertical: TextAlignVertical.top,
- maxLines: null,
- minLines: null,
- expands: true,
- hintText: s.enter_diet_description,
- onChanged: (value) {
- context.read().setDetails(value);
- },
- ),
- ),
- SizedBox(height: 12),
- DietDurationDropDownWidget(),
- SizedBox(height: 12),
- StatusDropDownWidget(),
- SizedBox(height: 16),
- DietTypeChoiceWidget(),
- SizedBox(height: 16),
- DietImagePickerWidget(),
- SizedBox(height: 16),
- ],
- ),
+ return BlocBuilder(
+ builder: (context, state) {
+ if (state.published) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ context.pop();
+ showPublishSuccessBottomSheet(
+ context: context,
+ text: '${state.dietData.name ?? 'Диета'}',
+ );
+ });
+ } else if (state.drafted) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ context.pop();
+ showDraftSuccessBottomSheet(
+ context: context,
+ text: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
+ );
+ });
+ }
+
+ return Scaffold(
+ backgroundColor: appTheme.gray100,
+ appBar: AppBar(
+ centerTitle: true,
+ title: Text(
+ s.create_diet,
+ textAlign: TextAlign.center,
+ overflow: TextOverflow.ellipsis,
+ softWrap: true,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
- SizedBox(height: 16),
- BlocBuilder(
- builder: (context, state) {
- return MainBackgroundBouble(
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ children: [
+ SizedBox(height: 8),
+ MainBackgroundBouble(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 16),
+ CustomTextFormField(
+ hintText: s.enter_diet_name,
+ hasError: state.dietNameError != null,
+ onChanged: (value) {
+ context.read().setName(value);
+ },
+ ),
+ if (state.dietNameError != null) ...[
+ SizedBox(
+ height: 4,
+ ),
+ CustomErrorWidget(error: state.dietNameError ?? ''),
+ ],
+ SizedBox(height: 12),
+ SizedBox(
+ height: 132,
+ child: CustomTextFormField(
+ hasError: state.dietDescriptionError != null,
+ textAlignVertical: TextAlignVertical.top,
+ maxLines: null,
+ minLines: null,
+ expands: true,
+ hintText: s.enter_diet_description,
+ onChanged: (value) {
+ context.read().setDetails(value);
+ },
+ ),
+ ),
+ if (state.dietDescriptionError != null) ...[
+ SizedBox(
+ height: 4,
+ ),
+ CustomErrorWidget(error: state.dietDescriptionError ?? ''),
+ ],
+ SizedBox(height: 12),
+ DietDurationDropDownWidget(),
+ SizedBox(height: 12),
+ StatusDropDownWidget(),
+ SizedBox(height: 16),
+ DietTypeChoiceWidget(),
+ SizedBox(height: 16),
+ DietImagePickerWidget(),
+ SizedBox(height: 16),
+ ],
+ ),
+ ),
+ SizedBox(height: 16),
+ MainBackgroundBouble(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Center(
@@ -106,71 +143,223 @@ class CreateDietScreenContent extends StatelessWidget {
),
),
),
- );
- },
- ),
- SizedBox(height: 8),
- BlocBuilder(
- builder: (context, state) {
- return ListView.separated(
+ ),
+ SizedBox(height: 8),
+ ListView.separated(
padding: const EdgeInsets.all(0),
itemBuilder: (context, index) => DayWidget(index: index),
itemCount: state.dietData.dailySchedule?.length ?? 0,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 8),
- );
- },
+ ),
+ SizedBox(height: 20),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ children: [
+ CustomOutlinedButton(
+ onPressed: () async {
+ await context.read().publishDiet(toDraft: true);
+ },
+ text: s.to_draft,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
+ buttonStyle: ButtonStyle(
+ side: WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
+ ),
+ ),
+ SizedBox(height: 12),
+ CustomOutlinedButton(
+ onPressed: () async {
+ final diet = await context.read().getDietForPreview();
+ if (diet == null) return;
+ GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
+ },
+ text: s.preview,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
+ buttonStyle: ButtonStyle(
+ side: WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
+ ),
+ ),
+ SizedBox(height: 12),
+ CustomOutlinedButton(
+ onPressed: () {
+ context.read().publishDiet();
+ },
+ text: s.publish,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ side: WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ ),
+ ),
+ ],
+ ),
+ ),
+ SizedBox(height: 20),
+ ],
),
- SizedBox(height: 20),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ ),
+ );
+ },
+ );
+ }
+
+ Future showDraftSuccessBottomSheet({
+ required BuildContext context,
+ required String text,
+ bool hasBlur = true,
+ bool enableDrag = true,
+ }) {
+ return showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ barrierColor: appTheme.transparentBlack,
+ backgroundColor: Colors.transparent,
+ elevation: 0,
+ useSafeArea: true,
+ enableDrag: enableDrag,
+ builder: (context) => GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(
+ sigmaX: hasBlur ? 24 : 0,
+ sigmaY: hasBlur ? 24 : 0,
+ tileMode: TileMode.mirror,
+ ),
+ child: SizedBox(
+ child: Container(
+ height: 200,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20),
+ topRight: Radius.circular(20),
+ ),
+ color: appTheme.whiteA700,
+ ),
child: Column(
children: [
- CustomOutlinedButton(
- onPressed: () {
- context.read().setDietState(2);
- },
- text: s.to_draft,
- height: 52,
- buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
- buttonStyle: ButtonStyle(
- side: WidgetStatePropertyAll(BorderSide.none),
- backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
+ SizedBox(
+ height: 20,
+ ),
+ BottomSheetHandlerBar(),
+ SizedBox(
+ height: 20,
+ ),
+ Text(
+ text,
+ textAlign: TextAlign.center,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ SizedBox(
+ height: 20,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: CustomOutlinedButton(
+ onPressed: () {
+ context.pop();
+ },
+ text: 'Хорошо',
+ height: 52,
+ buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ side: WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ ),
),
),
- SizedBox(height: 12),
- CustomOutlinedButton(
- onPressed: () async {
- final diet = await context.read().getDietForPreview();
- GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
- },
- text: s.preview,
- height: 52,
- buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
- buttonStyle: ButtonStyle(
- side: WidgetStatePropertyAll(BorderSide.none),
- backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
- ),
- ),
- SizedBox(height: 12),
- CustomOutlinedButton(
- onPressed: () {
- context.read().publishDiet();
- },
- text: s.publish,
- height: 52,
- buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
- buttonStyle: ButtonStyle(
- side: WidgetStatePropertyAll(BorderSide.none),
- backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
- ),
+ SizedBox(
+ height: 20,
),
],
),
),
- SizedBox(height: 20),
- ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Future showPublishSuccessBottomSheet({
+ required BuildContext context,
+ required String text,
+ bool hasBlur = true,
+ bool enableDrag = true,
+ }) {
+ return showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ barrierColor: appTheme.transparentBlack,
+ backgroundColor: Colors.transparent,
+ elevation: 0,
+ useSafeArea: true,
+ enableDrag: enableDrag,
+ builder: (context) => GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(
+ sigmaX: hasBlur ? 24 : 0,
+ sigmaY: hasBlur ? 24 : 0,
+ tileMode: TileMode.mirror,
+ ),
+ child: SizedBox(
+ child: Container(
+ height: 200,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20),
+ topRight: Radius.circular(20),
+ ),
+ color: appTheme.whiteA700,
+ ),
+ child: Column(
+ children: [
+ SizedBox(
+ height: 20,
+ ),
+ BottomSheetHandlerBar(),
+ SizedBox(
+ height: 20,
+ ),
+ Text(
+ text,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ Text(
+ 'опубликованна',
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ SizedBox(
+ height: 20,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: CustomOutlinedButton(
+ onPressed: () {
+ context.pop();
+ },
+ text: 'Хорошо',
+ height: 52,
+ buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ side: WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ ),
+ ),
+ ),
+ SizedBox(
+ height: 20,
+ ),
+ ],
+ ),
+ ),
+ ),
),
),
);
diff --git a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart
index 8f3984bd..666643a8 100644
--- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart
+++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart
@@ -1,5 +1,8 @@
import 'dart:convert';
+import 'dart:io';
+import 'dart:developer';
import 'package:eatvio_app/core/const.dart';
+import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
@@ -18,11 +21,11 @@ class CreateDietCubit extends Cubit {
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
void setDetails(String text) {
- emit(state.copyWith(dietData: state.dietData.copyWith(details: text)));
+ emit(state.copyWith(dietData: state.dietData.copyWith(details: text), dietDescriptionError: null));
}
void setName(String name) {
- emit(state.copyWith(dietData: state.dietData.copyWith(name: name)));
+ emit(state.copyWith(dietData: state.dietData.copyWith(name: name), dietNameError: null));
}
void setDuration(int index) {
@@ -70,6 +73,7 @@ class CreateDietCubit extends Cubit {
dietData: state.dietData.copyWith(
duration: durationValues[index],
),
+ dietDurationError: null,
),
);
}
@@ -79,21 +83,25 @@ class CreateDietCubit extends Cubit {
}
void setImage(XFile? image) {
- emit(state.copyWith(dietData: state.dietData.copyWith(image: image)));
+ emit(state.copyWith(dietData: state.dietData.copyWith(image: image), dietImageError: null));
}
void setDailySchedule(List>> dailySchedule) {
- emit(state.copyWith(dietData: state.dietData.copyWith(dailySchedule: dailySchedule)));
+ emit(state.copyWith(dietData: state.dietData.copyWith(dailySchedule: dailySchedule), dietDurationError: null));
}
void setDietState(int index) {
- emit(state.copyWith(dietStateIndex: index, dietData: state.dietData.copyWith(state: _dietStateValues[index])));
+ emit(state.copyWith(dietStateIndex: index, dietData: state.dietData.copyWith(state: _dietStateValues[index]), dietStateError: null));
}
void setDietType({required String key, required bool value}) {
final type = Map.from(state.dietData.type);
type[key] = value;
- emit(state.copyWith(dietData: state.dietData.copyWith(type: type)));
+ emit(
+ state.copyWith(
+ dietData: state.dietData.copyWith(type: type),
+ ),
+ );
}
void addPortion({required int dayIndex, required int mealIndex, required DailySchedule portion}) {
@@ -108,6 +116,7 @@ class CreateDietCubit extends Cubit {
final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule);
emit(state.copyWith(dietData: newDietData));
+ _isDayValid(dayIndex);
}
}
@@ -170,10 +179,12 @@ class CreateDietCubit extends Cubit {
deepCopyOfDailySchedule[dayIndex] = copiedDay;
final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule);
emit(state.copyWith(dietData: newDietData));
+ _isDayValid(dayIndex);
}
}
- Future getDietForPreview() async {
+ Future getDietForPreview() async {
+ if (!_isFromValid()) return null;
final authorProfile = await profileRepository.getMyProfile();
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? [];
final imageInBase64 = base64.encode(imageBytes);
@@ -232,10 +243,10 @@ class CreateDietCubit extends Cubit {
duration: state.dietData.duration ?? 0,
state: state.dietData.state ?? '',
type: DietType(
- meat: state.dietData.type[0],
- veg: state.dietData.type[1],
- carb: state.dietData.type[2],
- light: state.dietData.type[3],
+ meat: state.dietData.type['meat'],
+ veg: state.dietData.type['veg'],
+ carb: state.dietData.type['carb'],
+ light: state.dietData.type['light'],
),
details: state.dietData.details ?? '',
dailySchedule: state.dietData.dailySchedule ?? [],
@@ -256,25 +267,110 @@ class CreateDietCubit extends Cubit {
return diet;
}
- Future publishDiet() async {
+ Future publishDiet({bool toDraft = false}) async {
+ if (!_isFromValid()) return null;
try {
- // final types = jsonEncode(state.dietData.type);
- // if (state.dietData.image == null) {
- // return;
- // }
- // final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
- // final diet = await createDietRepository.createDiet(
- // name: state.dietData.name,
- // duration: state.dietData.duration,
- // state: state.dietData.state,
- // image: image,
- // type: types,
- // details: state.dietData.details,
- // );
+ final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
+ final dietState = toDraft ? _dietStateValues.last : state.dietData.state;
+ final diet = await createDietRepository.createDiet(
+ name: state.dietData.name,
+ duration: state.dietData.duration,
+ state: dietState,
+ image: image,
+ meatType: state.dietData.type['meat'] == true ? '1' : '0',
+ carbType: state.dietData.type['carb'] == true ? '1' : '0',
+ vegType: state.dietData.type['veg'] == true ? '1' : '0',
+ lightType: state.dietData.type['light'] == true ? '1' : '0',
+ details: state.dietData.details,
+ );
+ final dailySchedule = state.dietData.dailySchedule;
+ createDietRepository.createDietDailySchedule(
+ id: diet.id,
+ model: {'daily_schedule': dailySchedule},
+ );
+ emit(state.copyWith(published: !toDraft, drafted: toDraft));
+ return state.dietData.name;
} catch (e, stackTrace) {
- print(e);
- print('---------------------------------');
- print(stackTrace);
+ log(e.toString(), stackTrace: stackTrace);
}
+ return null;
+ }
+
+ bool _isFromValid() {
+ bool result = true;
+ if ((state.dietData.name?.length ?? 0) < 3) {
+ result = false;
+ emit(state.copyWith(dietNameError: 'Длинна названия не может быть меньше 3 символов'));
+ }
+ if (state.dietData.name == null || state.dietData.name == '') {
+ result = false;
+ emit(state.copyWith(dietNameError: 'Поле не заполненно'));
+ }
+ if ((state.dietData.details?.length ?? 0) < 3) {
+ result = false;
+ emit(state.copyWith(dietDescriptionError: 'Длинна описания не может быть меньше 3 символов'));
+ }
+ if (state.dietData.details == null || state.dietData.details == '') {
+ result = false;
+ emit(state.copyWith(dietDescriptionError: 'Поле не заполненно'));
+ }
+ if (state.dietData.duration == null) {
+ result = false;
+ emit(state.copyWith(dietDurationError: 'Срок диеты не выбран'));
+ }
+ if (state.dietData.state == null) {
+ result = false;
+ emit(state.copyWith(dietStateError: 'Статус диеты не выбран'));
+ }
+ if (state.dietData.image == null) {
+ result = false;
+ emit(state.copyWith(dietImageError: 'Не добавлена обложка'));
+ }
+ result = _isDailyScheduleValid();
+ return result;
+ }
+
+ bool _isDailyScheduleValid() {
+ final dailySchedule = state.dietData.dailySchedule;
+ bool result = true;
+ if (dailySchedule == null) {
+ result = false;
+ } else {
+ final dailyScheduleErrors = state.dailyScheduleErrors.map((element) {
+ return element.map((element) => element).toList();
+ }).toList();
+ for (final (dayIndex, day) in dailySchedule.indexed) {
+ for (final (mealIndex, meal) in day.indexed) {
+ if (meal.isEmpty) {
+ result = false;
+ dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты';
+ }
+ }
+ }
+ emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
+ }
+ return result;
+ }
+
+ bool _isDayValid(int dayIndex) {
+ final dailySchedule = state.dietData.dailySchedule;
+ bool result = true;
+ if (dailySchedule == null) {
+ result = false;
+ } else {
+ final dailyScheduleErrors = state.dailyScheduleErrors.map((element) {
+ return element.map((element) => element).toList();
+ }).toList();
+ for (final (mealIndex, meal) in dailySchedule[dayIndex].indexed) {
+ if (meal.isEmpty) {
+ result = false;
+ dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты';
+ } else {
+ dailyScheduleErrors[dayIndex][mealIndex] = '';
+ }
+ }
+ emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
+ }
+ return result;
}
}
diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.dart
index 0c554d28..16bec064 100644
--- a/lib/features/create_diet/presentation/cubit/create_diet_state.dart
+++ b/lib/features/create_diet/presentation/cubit/create_diet_state.dart
@@ -12,11 +12,20 @@ class CreateDietState with _$CreateDietState {
required String? errorMessage,
required CreateDietModel dietData,
List>? copiedDay,
+ String? dietNameError,
+ String? dietDescriptionError,
+ String? dietDurationError,
+ String? dietStateError,
+ String? dietImageError,
+ required List> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex,
+ @Default(false) bool published,
+ @Default(false) bool drafted,
}) = _CreateDietState;
factory CreateDietState.initial() => CreateDietState(
+ dailyScheduleErrors: List.filled(21, ['', '', '', '', '']),
isLoading: false,
errorMessage: null,
dietData: CreateDietModel(
diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart
index c58a70e9..a49cbb70 100644
--- a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart
+++ b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart
@@ -21,8 +21,17 @@ mixin _$CreateDietState {
CreateDietModel get dietData => throw _privateConstructorUsedError;
List>? get copiedDay =>
throw _privateConstructorUsedError;
+ String? get dietNameError => throw _privateConstructorUsedError;
+ String? get dietDescriptionError => throw _privateConstructorUsedError;
+ String? get dietDurationError => throw _privateConstructorUsedError;
+ String? get dietStateError => throw _privateConstructorUsedError;
+ String? get dietImageError => throw _privateConstructorUsedError;
+ List> get dailyScheduleErrors =>
+ throw _privateConstructorUsedError;
int? get durationIndex => throw _privateConstructorUsedError;
int? get dietStateIndex => throw _privateConstructorUsedError;
+ bool get published => throw _privateConstructorUsedError;
+ bool get drafted => throw _privateConstructorUsedError;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -42,8 +51,16 @@ abstract class $CreateDietStateCopyWith<$Res> {
String? errorMessage,
CreateDietModel dietData,
List>? copiedDay,
+ String? dietNameError,
+ String? dietDescriptionError,
+ String? dietDurationError,
+ String? dietStateError,
+ String? dietImageError,
+ List> dailyScheduleErrors,
int? durationIndex,
- int? dietStateIndex});
+ int? dietStateIndex,
+ bool published,
+ bool drafted});
$CreateDietModelCopyWith<$Res> get dietData;
}
@@ -67,8 +84,16 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
+ Object? dietNameError = freezed,
+ Object? dietDescriptionError = freezed,
+ Object? dietDurationError = freezed,
+ Object? dietStateError = freezed,
+ Object? dietImageError = freezed,
+ Object? dailyScheduleErrors = null,
Object? durationIndex = freezed,
Object? dietStateIndex = freezed,
+ Object? published = null,
+ Object? drafted = null,
}) {
return _then(_value.copyWith(
isLoading: null == isLoading
@@ -87,6 +112,30 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
? _value.copiedDay
: copiedDay // ignore: cast_nullable_to_non_nullable
as List>?,
+ dietNameError: freezed == dietNameError
+ ? _value.dietNameError
+ : dietNameError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietDescriptionError: freezed == dietDescriptionError
+ ? _value.dietDescriptionError
+ : dietDescriptionError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietDurationError: freezed == dietDurationError
+ ? _value.dietDurationError
+ : dietDurationError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietStateError: freezed == dietStateError
+ ? _value.dietStateError
+ : dietStateError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietImageError: freezed == dietImageError
+ ? _value.dietImageError
+ : dietImageError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dailyScheduleErrors: null == dailyScheduleErrors
+ ? _value.dailyScheduleErrors
+ : dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
+ as List>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -95,6 +144,14 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
? _value.dietStateIndex
: dietStateIndex // ignore: cast_nullable_to_non_nullable
as int?,
+ published: null == published
+ ? _value.published
+ : published // ignore: cast_nullable_to_non_nullable
+ as bool,
+ drafted: null == drafted
+ ? _value.drafted
+ : drafted // ignore: cast_nullable_to_non_nullable
+ as bool,
) as $Val);
}
@@ -122,8 +179,16 @@ abstract class _$$CreateDietStateImplCopyWith<$Res>
String? errorMessage,
CreateDietModel dietData,
List>? copiedDay,
+ String? dietNameError,
+ String? dietDescriptionError,
+ String? dietDurationError,
+ String? dietStateError,
+ String? dietImageError,
+ List> dailyScheduleErrors,
int? durationIndex,
- int? dietStateIndex});
+ int? dietStateIndex,
+ bool published,
+ bool drafted});
@override
$CreateDietModelCopyWith<$Res> get dietData;
@@ -146,8 +211,16 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
+ Object? dietNameError = freezed,
+ Object? dietDescriptionError = freezed,
+ Object? dietDurationError = freezed,
+ Object? dietStateError = freezed,
+ Object? dietImageError = freezed,
+ Object? dailyScheduleErrors = null,
Object? durationIndex = freezed,
Object? dietStateIndex = freezed,
+ Object? published = null,
+ Object? drafted = null,
}) {
return _then(_$CreateDietStateImpl(
isLoading: null == isLoading
@@ -166,6 +239,30 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
? _value._copiedDay
: copiedDay // ignore: cast_nullable_to_non_nullable
as List>?,
+ dietNameError: freezed == dietNameError
+ ? _value.dietNameError
+ : dietNameError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietDescriptionError: freezed == dietDescriptionError
+ ? _value.dietDescriptionError
+ : dietDescriptionError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietDurationError: freezed == dietDurationError
+ ? _value.dietDurationError
+ : dietDurationError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietStateError: freezed == dietStateError
+ ? _value.dietStateError
+ : dietStateError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dietImageError: freezed == dietImageError
+ ? _value.dietImageError
+ : dietImageError // ignore: cast_nullable_to_non_nullable
+ as String?,
+ dailyScheduleErrors: null == dailyScheduleErrors
+ ? _value._dailyScheduleErrors
+ : dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
+ as List>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -174,6 +271,14 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
? _value.dietStateIndex
: dietStateIndex // ignore: cast_nullable_to_non_nullable
as int?,
+ published: null == published
+ ? _value.published
+ : published // ignore: cast_nullable_to_non_nullable
+ as bool,
+ drafted: null == drafted
+ ? _value.drafted
+ : drafted // ignore: cast_nullable_to_non_nullable
+ as bool,
));
}
}
@@ -186,9 +291,18 @@ class _$CreateDietStateImpl implements _CreateDietState {
required this.errorMessage,
required this.dietData,
final List>? copiedDay,
+ this.dietNameError,
+ this.dietDescriptionError,
+ this.dietDurationError,
+ this.dietStateError,
+ this.dietImageError,
+ required final List> dailyScheduleErrors,
this.durationIndex,
- this.dietStateIndex})
- : _copiedDay = copiedDay;
+ this.dietStateIndex,
+ this.published = false,
+ this.drafted = false})
+ : _copiedDay = copiedDay,
+ _dailyScheduleErrors = dailyScheduleErrors;
@override
final bool isLoading;
@@ -206,14 +320,39 @@ class _$CreateDietStateImpl implements _CreateDietState {
return EqualUnmodifiableListView(value);
}
+ @override
+ final String? dietNameError;
+ @override
+ final String? dietDescriptionError;
+ @override
+ final String? dietDurationError;
+ @override
+ final String? dietStateError;
+ @override
+ final String? dietImageError;
+ final List> _dailyScheduleErrors;
+ @override
+ List> get dailyScheduleErrors {
+ if (_dailyScheduleErrors is EqualUnmodifiableListView)
+ return _dailyScheduleErrors;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_dailyScheduleErrors);
+ }
+
@override
final int? durationIndex;
@override
final int? dietStateIndex;
+ @override
+ @JsonKey()
+ final bool published;
+ @override
+ @JsonKey()
+ final bool drafted;
@override
String toString() {
- return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex)';
+ return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, dietNameError: $dietNameError, dietDescriptionError: $dietDescriptionError, dietDurationError: $dietDurationError, dietStateError: $dietStateError, dietImageError: $dietImageError, dailyScheduleErrors: $dailyScheduleErrors, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex, published: $published, drafted: $drafted)';
}
@override
@@ -229,10 +368,25 @@ class _$CreateDietStateImpl implements _CreateDietState {
other.dietData == dietData) &&
const DeepCollectionEquality()
.equals(other._copiedDay, _copiedDay) &&
+ (identical(other.dietNameError, dietNameError) ||
+ other.dietNameError == dietNameError) &&
+ (identical(other.dietDescriptionError, dietDescriptionError) ||
+ other.dietDescriptionError == dietDescriptionError) &&
+ (identical(other.dietDurationError, dietDurationError) ||
+ other.dietDurationError == dietDurationError) &&
+ (identical(other.dietStateError, dietStateError) ||
+ other.dietStateError == dietStateError) &&
+ (identical(other.dietImageError, dietImageError) ||
+ other.dietImageError == dietImageError) &&
+ const DeepCollectionEquality()
+ .equals(other._dailyScheduleErrors, _dailyScheduleErrors) &&
(identical(other.durationIndex, durationIndex) ||
other.durationIndex == durationIndex) &&
(identical(other.dietStateIndex, dietStateIndex) ||
- other.dietStateIndex == dietStateIndex));
+ other.dietStateIndex == dietStateIndex) &&
+ (identical(other.published, published) ||
+ other.published == published) &&
+ (identical(other.drafted, drafted) || other.drafted == drafted));
}
@override
@@ -242,8 +396,16 @@ class _$CreateDietStateImpl implements _CreateDietState {
errorMessage,
dietData,
const DeepCollectionEquality().hash(_copiedDay),
+ dietNameError,
+ dietDescriptionError,
+ dietDurationError,
+ dietStateError,
+ dietImageError,
+ const DeepCollectionEquality().hash(_dailyScheduleErrors),
durationIndex,
- dietStateIndex);
+ dietStateIndex,
+ published,
+ drafted);
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -261,8 +423,16 @@ abstract class _CreateDietState implements CreateDietState {
required final String? errorMessage,
required final CreateDietModel dietData,
final List>? copiedDay,
+ final String? dietNameError,
+ final String? dietDescriptionError,
+ final String? dietDurationError,
+ final String? dietStateError,
+ final String? dietImageError,
+ required final List> dailyScheduleErrors,
final int? durationIndex,
- final int? dietStateIndex}) = _$CreateDietStateImpl;
+ final int? dietStateIndex,
+ final bool published,
+ final bool drafted}) = _$CreateDietStateImpl;
@override
bool get isLoading;
@@ -273,9 +443,25 @@ abstract class _CreateDietState implements CreateDietState {
@override
List>? get copiedDay;
@override
+ String? get dietNameError;
+ @override
+ String? get dietDescriptionError;
+ @override
+ String? get dietDurationError;
+ @override
+ String? get dietStateError;
+ @override
+ String? get dietImageError;
+ @override
+ List> get dailyScheduleErrors;
+ @override
int? get durationIndex;
@override
int? get dietStateIndex;
+ @override
+ bool get published;
+ @override
+ bool get drafted;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart
index 2da26a46..4680f763 100644
--- a/lib/features/create_diet/presentation/diet_preview_screen.dart
+++ b/lib/features/create_diet/presentation/diet_preview_screen.dart
@@ -297,7 +297,7 @@ class _DietPreviewScreenState extends State {
),
),
SizedBox(height: 12),
- DietInfoRow(diet: widget.diet),
+ InfoRow(diet: widget.diet),
ExpandableText(
text: widget.diet.details,
),
@@ -306,6 +306,7 @@ class _DietPreviewScreenState extends State {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.diet.author,
+ title: s.diet_author,
),
),
SizedBox(height: 16),
diff --git a/lib/features/create_diet/presentation/widgets/day_widget.dart b/lib/features/create_diet/presentation/widgets/day_widget.dart
index 732d6a7a..ba97708d 100644
--- a/lib/features/create_diet/presentation/widgets/day_widget.dart
+++ b/lib/features/create_diet/presentation/widgets/day_widget.dart
@@ -9,6 +9,7 @@ import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/add_meal_widget.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
@@ -34,6 +35,7 @@ class _DayWidgetState extends State {
return BlocBuilder(
builder: (context, state) {
return MainBackgroundBouble(
+ hasError: state.dailyScheduleErrors[widget.index].where((element) => element.isNotEmpty).isNotEmpty,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -214,6 +216,10 @@ class _DayWidgetState extends State {
),
],
),
+ if (state.dailyScheduleErrors[widget.index][_selectedMealIndex].isNotEmpty) ...[
+ SizedBox(height: 12),
+ CustomErrorWidget(error: state.dailyScheduleErrors[widget.index][_selectedMealIndex]),
+ ],
SizedBox(height: 20),
],
),
diff --git a/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart
index fda76a4f..97987b30 100644
--- a/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart
+++ b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
@@ -36,28 +37,40 @@ class _DietDurationDropDownWidgetState extends State
final s = S.of(context);
return BlocBuilder(
builder: (context, state) {
- return CustomDropDownButton(
- hint: Text(
- s.choose_diet_duration,
- style: TextStyles.textMMedium.copyWith(color: appTheme.black),
- ),
- onChanged: (value) {
- if (value != null) {
- context.read().setDuration(durationDropDownTitles.indexOf(value));
- }
- },
- items: durationDropDownTitles
- .mapIndexed(
- (index, value) => DropdownMenuItem(
- child: Text(
- durationDropDownTitles[index],
- style: TextStyles.textMMedium.copyWith(color: appTheme.black),
- ),
- value: durationDropDownTitles[index],
- ),
- )
- .toList(),
- value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ CustomDropDownButton(
+ hasError: state.dietDurationError != null,
+ hint: Text(
+ s.choose_diet_duration,
+ style: TextStyles.textSMedium.copyWith(color: appTheme.black),
+ ),
+ onChanged: (value) {
+ if (value != null) {
+ context.read().setDuration(durationDropDownTitles.indexOf(value));
+ }
+ },
+ items: durationDropDownTitles
+ .mapIndexed(
+ (index, value) => DropdownMenuItem(
+ child: Text(
+ durationDropDownTitles[index],
+ style: TextStyles.textMMedium.copyWith(color: appTheme.black),
+ ),
+ value: durationDropDownTitles[index],
+ ),
+ )
+ .toList(),
+ value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
+ ),
+ if (state.dietDurationError != null) ...[
+ SizedBox(
+ height: 4,
+ ),
+ CustomErrorWidget(error: state.dietDurationError ?? ''),
+ ],
+ ],
);
},
);
diff --git a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart
index 42ba88a2..0e195a09 100644
--- a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart
+++ b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@@ -70,74 +71,79 @@ class _DietImagePickerWidgetState extends State {
final s = S.of(context);
return BlocBuilder(
builder: (context, state) {
- return GestureDetector(
- onTap: () {
- _showImagePickerOptions(s, context.read());
- },
- child: Container(
- height: 240,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(8),
- image: state.dietData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.dietData.image?.path ?? ''))) : null,
- color: appTheme.gray50,
+ return Column(
+ children: [
+ GestureDetector(
+ onTap: () {
+ _showImagePickerOptions(s, context.read());
+ },
+ child: Container(
+ height: 240,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ image: state.dietData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.dietData.image?.path ?? ''))) : null,
+ color: appTheme.gray50,
+ ),
+ width: MediaQuery.sizeOf(context).width,
+ padding: state.dietData.image != null ? null : EdgeInsets.all(8),
+ child: state.dietData.image != null
+ ? Padding(
+ padding: const EdgeInsets.only(top: 6, right: 6),
+ child: GestureDetector(
+ onTap: () {
+ context.read().setImage(null);
+ },
+ child: Align(
+ alignment: Alignment.topRight,
+ child: Container(
+ width: 34,
+ height: 34,
+ decoration: BoxDecoration(
+ color: appTheme.dividerColor,
+ shape: BoxShape.circle,
+ ),
+ child: Icon(
+ Icons.close_rounded,
+ color: Colors.grey,
+ size: 16,
+ ),
+ ),
+ ),
+ ),
+ )
+ : Stack(
+ children: [
+ DottedBorder(
+ stackFit: StackFit.expand,
+ dashPattern: [14, 14],
+ color: state.dietImageError != null ? appTheme.red : appTheme.dottedBorder,
+ borderType: BorderType.RRect,
+ radius: Radius.circular(12),
+ child: SizedBox.expand(),
+ ),
+ Center(
+ child: Column(
+ children: [
+ SizedBox(
+ height: 48,
+ ),
+ SvgPicture.asset('assets/icons/eatvio_logo.svg'),
+ SizedBox(
+ height: 24,
+ ),
+ Text(
+ s.add_cover,
+ style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
),
- width: MediaQuery.sizeOf(context).width,
- padding: state.dietData.image != null ? null : EdgeInsets.all(8),
- child: state.dietData.image != null
- ? Padding(
- padding: const EdgeInsets.only(top: 6, right: 6),
- child: GestureDetector(
- onTap: () {
- context.read().setImage(null);
- },
- child: Align(
- alignment: Alignment.topRight,
- child: Container(
- width: 34,
- height: 34,
- decoration: BoxDecoration(
- color: appTheme.dividerColor,
- shape: BoxShape.circle,
- ),
- child: Icon(
- Icons.close_rounded,
- color: Colors.grey,
- size: 16,
- ),
- ),
- ),
- ),
- )
- : Stack(
- children: [
- DottedBorder(
- stackFit: StackFit.expand,
- dashPattern: [14, 14],
- color: appTheme.dottedBorder,
- borderType: BorderType.RRect,
- radius: Radius.circular(12),
- child: SizedBox.expand(),
- ),
- Center(
- child: Column(
- children: [
- SizedBox(
- height: 48,
- ),
- SvgPicture.asset('assets/icons/eatvio_logo.svg'),
- SizedBox(
- height: 24,
- ),
- Text(
- s.add_cover,
- style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
+ if (state.dietImageError != null) ...[SizedBox(height: 8), CustomErrorWidget(error: state.dietImageError ?? '')],
+ ],
);
},
);
diff --git a/lib/features/create_diet/presentation/widgets/error_widget.dart b/lib/features/create_diet/presentation/widgets/error_widget.dart
new file mode 100644
index 00000000..bd9eaf9d
--- /dev/null
+++ b/lib/features/create_diet/presentation/widgets/error_widget.dart
@@ -0,0 +1,26 @@
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class CustomErrorWidget extends StatelessWidget {
+ const CustomErrorWidget({super.key, required this.error});
+ final String error;
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ SvgPicture.asset('assets/icons/error_icon.svg', width: 14, height: 14),
+ SizedBox(
+ width: 2,
+ ),
+ Expanded(
+ child: Text(
+ error,
+ style: TextStyles.textSMedium.copyWith(color: appTheme.red),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart
index d2db75ee..47443b58 100644
--- a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart
+++ b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart
@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -33,28 +34,40 @@ class _StatusDropDownWidgetState extends State {
final s = S.of(context);
return BlocBuilder(
builder: (context, state) {
- return CustomDropDownButton(
- hint: Text(
- s.diet_state,
- style: TextStyles.textMMedium.copyWith(color: appTheme.black),
- ),
- onChanged: (value) {
- if (value != null) {
- context.read().setDietState(dietStateTitles.indexOf(value));
- }
- },
- items: dietStateTitles
- .mapIndexed(
- (index, value) => DropdownMenuItem(
- child: Text(
- dietStateTitles[index],
- style: TextStyles.textMMedium.copyWith(color: appTheme.black),
- ),
- value: dietStateTitles[index],
- ),
- )
- .toList(),
- value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0],
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ CustomDropDownButton(
+ hasError: state.dietStateError != null,
+ hint: Text(
+ s.diet_state,
+ style: TextStyles.textSMedium.copyWith(color: appTheme.black),
+ ),
+ onChanged: (value) {
+ if (value != null) {
+ context.read().setDietState(dietStateTitles.indexOf(value));
+ }
+ },
+ items: dietStateTitles
+ .mapIndexed(
+ (index, value) => DropdownMenuItem(
+ child: Text(
+ dietStateTitles[index],
+ style: TextStyles.textMMedium.copyWith(color: appTheme.black),
+ ),
+ value: dietStateTitles[index],
+ ),
+ )
+ .toList(),
+ value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0],
+ ),
+ if (state.dietStateError != null) ...[
+ SizedBox(
+ height: 4,
+ ),
+ CustomErrorWidget(error: state.dietStateError ?? ''),
+ ],
+ ],
);
},
);
diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart
index 7d5a99db..1dbed20d 100644
--- a/lib/features/create_diet/repositories/create_diet_repository.dart
+++ b/lib/features/create_diet/repositories/create_diet_repository.dart
@@ -7,11 +7,14 @@ class CreateDietRepository {
final CreateDietService createDietService;
CreateDietRepository({required this.createDietService});
- Future createDiet({
- String? name,
- int? duration,
- String? state,
- String? type,
+ Future createDiet({
+ String? name,
+ int? duration,
+ String? state,
+ String? meatType,
+ String? carbType,
+ String? vegType,
+ String? lightType,
String? details,
File? image,
}) {
@@ -19,7 +22,10 @@ class CreateDietRepository {
name: name,
duration: duration,
state: state,
- type: type,
+ meatType: meatType,
+ carbType: carbType,
+ vegType: vegType,
+ lightType: lightType,
details: details,
image: image,
);
@@ -29,9 +35,13 @@ class CreateDietRepository {
required int id,
required Map model,
}) {
- return createDietService.createDietDailySchedule(
- id: id,
- model: model,
- );
+ try {
+ return createDietService.createDietDailySchedule(
+ id: id,
+ model: model,
+ );
+ } catch (e) {
+ rethrow;
+ }
}
}
diff --git a/lib/features/create_diet/services/create_diet_service.dart b/lib/features/create_diet/services/create_diet_service.dart
index ab48669a..07432e3a 100644
--- a/lib/features/create_diet/services/create_diet_service.dart
+++ b/lib/features/create_diet/services/create_diet_service.dart
@@ -12,11 +12,14 @@ abstract class CreateDietService {
@POST('/api/diets/create')
@MultiPart()
- Future createDiet({
+ Future createDiet({
@Part(name: 'name') String? name,
@Part(name: 'duration') int? duration,
@Part(name: 'state') String? state,
- @Part(name: 'type') String? type,
+ @Part(name: 'type[meat]') String? meatType,
+ @Part(name: 'type[carb]') String? carbType,
+ @Part(name: 'type[veg]') String? vegType,
+ @Part(name: 'type[light]') String? lightType,
@Part(name: 'details') String? details,
@Part(name: 'image') File? image,
});
diff --git a/lib/features/create_diet/services/create_diet_service.g.dart b/lib/features/create_diet/services/create_diet_service.g.dart
index 23d1c97c..0c5a4606 100644
--- a/lib/features/create_diet/services/create_diet_service.g.dart
+++ b/lib/features/create_diet/services/create_diet_service.g.dart
@@ -22,11 +22,14 @@ class _CreateDietService implements CreateDietService {
final ParseErrorLogger? errorLogger;
@override
- Future createDiet({
+ Future createDiet({
String? name,
int? duration,
String? state,
- String? type,
+ String? meatType,
+ String? carbType,
+ String? vegType,
+ String? lightType,
String? details,
File? image,
}) async {
@@ -53,10 +56,28 @@ class _CreateDietService implements CreateDietService {
state,
));
}
- if (type != null) {
+ if (meatType != null) {
_data.fields.add(MapEntry(
- 'type',
- type,
+ 'type[meat]',
+ meatType,
+ ));
+ }
+ if (carbType != null) {
+ _data.fields.add(MapEntry(
+ 'type[carb]',
+ carbType,
+ ));
+ }
+ if (vegType != null) {
+ _data.fields.add(MapEntry(
+ 'type[veg]',
+ vegType,
+ ));
+ }
+ if (lightType != null) {
+ _data.fields.add(MapEntry(
+ 'type[light]',
+ lightType,
));
}
if (details != null) {
@@ -76,7 +97,7 @@ class _CreateDietService implements CreateDietService {
));
}
}
- final _options = _setStreamType(Options(
+ final _options = _setStreamType(Options(
method: 'POST',
headers: _headers,
extra: _extra,
@@ -94,9 +115,9 @@ class _CreateDietService implements CreateDietService {
baseUrl,
)));
final _result = await _dio.fetch