This commit is contained in:
BLoC
2025-02-12 15:04:47 +03:00
61 changed files with 2211 additions and 1170 deletions

View File

@@ -59,7 +59,6 @@ class AppRouter {
path: '/auth-screen',
name: 'authScreen',
redirect: (context, state) async {
// TokenStorage.removeTokenInStorage();
if (await TokenStorage.haveTokenInStorage()) {
return '/my-profile-screen';
}
@@ -292,7 +291,10 @@ class AppRouter {
GoRoute(
path: CreateDietScreen.path,
name: CreateDietScreen.name,
builder: (context, state) => CreateDietScreen(),
builder: (context, state) {
final diet = state.extra as Diet?;
return CreateDietScreen(diet: diet);
},
),
GoRoute(
path: DietPreviewScreen.path,

View File

@@ -4,42 +4,44 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
Future<void> showBottomSheetWithBlur({
required BuildContext context,
required Widget child,
double height = 200,
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: height,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: appTheme.whiteA700,
required BuildContext context,
required Widget child,
double height = 200,
bool hasBlur = true,
bool enableDrag = true,
bool userRootNavigator = false,
}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: appTheme.transparentBlack,
backgroundColor: Colors.transparent,
useRootNavigator: userRootNavigator,
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: height,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
child: child,
color: appTheme.whiteA700,
),
child: child,
),
),
),
);
}
),
);
}

View File

@@ -9,12 +9,15 @@ class CreateDietModel with _$CreateDietModel {
const CreateDietModel._();
const factory CreateDietModel({
String? imageUrl,
String? name,
String? details,
String? state,
DietState? state,
int? duration,
required Map<String, bool> type,
XFile? image,
List<List<List<DailySchedule>>>? dailySchedule,
}) = _CreateDietModel;
}

View File

@@ -16,9 +16,10 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$CreateDietModel {
String? get imageUrl => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
String? get details => throw _privateConstructorUsedError;
String? get state => throw _privateConstructorUsedError;
DietState? get state => throw _privateConstructorUsedError;
int? get duration => throw _privateConstructorUsedError;
Map<String, bool> get type => throw _privateConstructorUsedError;
XFile? get image => throw _privateConstructorUsedError;
@@ -39,9 +40,10 @@ abstract class $CreateDietModelCopyWith<$Res> {
_$CreateDietModelCopyWithImpl<$Res, CreateDietModel>;
@useResult
$Res call(
{String? name,
{String? imageUrl,
String? name,
String? details,
String? state,
DietState? state,
int? duration,
Map<String, bool> type,
XFile? image,
@@ -63,6 +65,7 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? imageUrl = freezed,
Object? name = freezed,
Object? details = freezed,
Object? state = freezed,
@@ -72,6 +75,10 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel>
Object? dailySchedule = freezed,
}) {
return _then(_value.copyWith(
imageUrl: freezed == imageUrl
? _value.imageUrl
: imageUrl // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
@@ -83,7 +90,7 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel>
state: freezed == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String?,
as DietState?,
duration: freezed == duration
? _value.duration
: duration // ignore: cast_nullable_to_non_nullable
@@ -113,9 +120,10 @@ abstract class _$$CreateDietModelImplCopyWith<$Res>
@override
@useResult
$Res call(
{String? name,
{String? imageUrl,
String? name,
String? details,
String? state,
DietState? state,
int? duration,
Map<String, bool> type,
XFile? image,
@@ -135,6 +143,7 @@ class __$$CreateDietModelImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? imageUrl = freezed,
Object? name = freezed,
Object? details = freezed,
Object? state = freezed,
@@ -144,6 +153,10 @@ class __$$CreateDietModelImplCopyWithImpl<$Res>
Object? dailySchedule = freezed,
}) {
return _then(_$CreateDietModelImpl(
imageUrl: freezed == imageUrl
? _value.imageUrl
: imageUrl // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
@@ -155,7 +168,7 @@ class __$$CreateDietModelImplCopyWithImpl<$Res>
state: freezed == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String?,
as DietState?,
duration: freezed == duration
? _value.duration
: duration // ignore: cast_nullable_to_non_nullable
@@ -180,7 +193,8 @@ class __$$CreateDietModelImplCopyWithImpl<$Res>
class _$CreateDietModelImpl extends _CreateDietModel {
const _$CreateDietModelImpl(
{this.name,
{this.imageUrl,
this.name,
this.details,
this.state,
this.duration,
@@ -191,12 +205,14 @@ class _$CreateDietModelImpl extends _CreateDietModel {
_dailySchedule = dailySchedule,
super._();
@override
final String? imageUrl;
@override
final String? name;
@override
final String? details;
@override
final String? state;
final DietState? state;
@override
final int? duration;
final Map<String, bool> _type;
@@ -221,7 +237,7 @@ class _$CreateDietModelImpl extends _CreateDietModel {
@override
String toString() {
return 'CreateDietModel(name: $name, details: $details, state: $state, duration: $duration, type: $type, image: $image, dailySchedule: $dailySchedule)';
return 'CreateDietModel(imageUrl: $imageUrl, name: $name, details: $details, state: $state, duration: $duration, type: $type, image: $image, dailySchedule: $dailySchedule)';
}
@override
@@ -229,6 +245,8 @@ class _$CreateDietModelImpl extends _CreateDietModel {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CreateDietModelImpl &&
(identical(other.imageUrl, imageUrl) ||
other.imageUrl == imageUrl) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.details, details) || other.details == details) &&
(identical(other.state, state) || other.state == state) &&
@@ -243,6 +261,7 @@ class _$CreateDietModelImpl extends _CreateDietModel {
@override
int get hashCode => Object.hash(
runtimeType,
imageUrl,
name,
details,
state,
@@ -263,9 +282,10 @@ class _$CreateDietModelImpl extends _CreateDietModel {
abstract class _CreateDietModel extends CreateDietModel {
const factory _CreateDietModel(
{final String? name,
{final String? imageUrl,
final String? name,
final String? details,
final String? state,
final DietState? state,
final int? duration,
required final Map<String, bool> type,
final XFile? image,
@@ -273,12 +293,14 @@ abstract class _CreateDietModel extends CreateDietModel {
_$CreateDietModelImpl;
const _CreateDietModel._() : super._();
@override
String? get imageUrl;
@override
String? get name;
@override
String? get details;
@override
String? get state;
DietState? get state;
@override
int? get duration;
@override

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.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/loader.dart';
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';
@@ -16,6 +17,7 @@ import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_
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/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -24,19 +26,37 @@ import 'package:go_router/go_router.dart';
class CreateDietScreen extends StatelessWidget {
static const name = 'createDietScreen';
static const path = '/create-diet-screen';
const CreateDietScreen({super.key});
const CreateDietScreen({super.key, this.diet});
final Diet? diet;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => sl<CreateDietCubit>(),
child: const CreateDietScreenContent(),
create: (context) => sl<CreateDietCubit>()..setInitialData(diet),
child: CreateDietScreenContent(diet: diet),
);
}
}
class CreateDietScreenContent extends StatelessWidget {
const CreateDietScreenContent({super.key});
class CreateDietScreenContent extends StatefulWidget {
CreateDietScreenContent({super.key, this.diet});
final Diet? diet;
@override
State<CreateDietScreenContent> createState() => _CreateDietScreenContentState();
}
class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
final nameController = TextEditingController();
final descriptionController = TextEditingController();
@override
void initState() {
super.initState();
if (widget.diet != null) {
nameController.text = widget.diet?.name ?? '';
descriptionController.text = widget.diet?.details ?? '';
}
}
@override
Widget build(BuildContext context) {
@@ -44,55 +64,12 @@ class CreateDietScreenContent extends StatelessWidget {
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
if (state.published) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.pop();
showBottomSheetWithBlur(
context: context,
child: Column(
children: [
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
'${state.dietData.name ?? 'Диета'}',
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,
),
],
),
);
Future.delayed(Duration(milliseconds: 500), () {
context.pop('${state.dietData.name ?? 'Диета'}');
});
} else if (state.drafted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.pop();
Future.delayed(Duration(milliseconds: 500), () {
context.pop('${state.dietData.name ?? 'Диета'}');
showBottomSheetWithBlur(
context: context,
child: Column(
@@ -116,7 +93,7 @@ class CreateDietScreenContent extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
context.pop();
Navigator.of(context, rootNavigator: true).pop();
},
text: 'Хорошо',
height: 52,
@@ -141,7 +118,7 @@ class CreateDietScreenContent extends StatelessWidget {
appBar: AppBar(
centerTitle: true,
title: Text(
s.create_diet,
widget.diet != null ? 'Редактирование диеты' : s.create_diet,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -160,6 +137,7 @@ class CreateDietScreenContent extends StatelessWidget {
SizedBox(height: 16),
CustomTextFormField(
hintText: s.enter_diet_name,
controller: nameController,
hasError: state.dietNameError != null,
onChanged: (value) {
context.read<CreateDietCubit>().setName(value);
@@ -176,6 +154,7 @@ class CreateDietScreenContent extends StatelessWidget {
height: 132,
child: CustomTextFormField(
hasError: state.dietDescriptionError != null,
controller: descriptionController,
textAlignVertical: TextAlignVertical.top,
maxLines: null,
minLines: null,
@@ -227,30 +206,19 @@ class CreateDietScreenContent extends StatelessWidget {
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 8),
),
SizedBox(height: 20),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
CustomOutlinedButton(
onPressed: () async {
await context.read<CreateDietCubit>().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<CreateDietCubit>().getDietForPreview();
if (diet == null) return;
GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
},
onPressed: state.isDietPublishing
? () {}
: () async {
final diet = await context.read<CreateDietCubit>().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),
@@ -260,18 +228,21 @@ class CreateDietScreenContent extends StatelessWidget {
),
),
SizedBox(height: 12),
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().publishDiet();
},
text: s.publish,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
if (state.isDietPublishing)
Loader()
else
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().publishDiet();
},
text: widget.diet != null ? s.save : s.publish,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
],
),
),
@@ -282,5 +253,5 @@ class CreateDietScreenContent extends StatelessWidget {
);
},
);
}
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'dart:developer';
import 'package:dio/dio.dart';
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';
@@ -16,10 +17,46 @@ class CreateDietCubit extends Cubit<CreateDietState> {
final CreateDietRepository createDietRepository;
final ProfileRepository profileRepository;
final GlossaryRepository glossaryRepository;
static const _dietStateValues = ['active', 'personal', 'draft'];
String? tempImagePath;
static const _dietStateValues = DietState.values;
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
Future<void> setInitialData(Diet? diet) async {
if (diet == null) return;
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
final countOfDays = diet.dailySchedule.length;
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
for (final dayEntry in diet.dailySchedule.entries) {
for (final mealEntry in dayEntry.value.entries) {
for (final product in mealEntry.value.values) {
dailySchedule[dayEntry.key - 1][mealEntry.key - 1].add(product);
}
}
}
final dietStateIndex = _dietStateValues.indexOf(diet.state);
final type = Map<String, bool>.fromIterable(dietTypeValues, value: (element) => false);
type['meat'] = diet.type.meat ?? false;
type['veg'] = diet.type.veg ?? false;
type['carb'] = diet.type.carb ?? false;
type['light'] = diet.type.light ?? false;
emit(
state.copyWith.dietData(
dailySchedule: dailySchedule,
details: diet.details,
state: diet.state,
name: diet.name,
duration: diet.duration,
type: type,
imageUrl: baseUrl + '/storage/' + diet.image,
),
);
emit(state.copyWith(dietStateIndex: dietStateIndex, durationIndex: durationValues.indexOf(diet.duration)));
emit(state.copyWith(isLoading: true));
}
void setDetails(String text) {
emit(state.copyWith(dietData: state.dietData.copyWith(details: text), dietDescriptionError: null));
}
@@ -82,16 +119,16 @@ class CreateDietCubit extends Cubit<CreateDietState> {
return await glossaryRepository.getGlossaryListByQuery(query);
}
void setImage(XFile? image) {
emit(state.copyWith(dietData: state.dietData.copyWith(image: image), dietImageError: null));
void setImage(XFile? image, {String? imageUrl}) {
emit(state.copyWith(dietData: state.dietData.copyWith(image: image, imageUrl: imageUrl), dietImageError: null));
}
void setDailySchedule(List<List<List<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]), dietStateError: null));
void setDietState(DietState dietState, int index) {
emit(state.copyWith(dietData: state.dietData.copyWith(state: dietState), dietStateIndex: index, dietStateError: null));
}
void setDietType({required String key, required bool value}) {
@@ -236,12 +273,26 @@ class CreateDietCubit extends Cubit<CreateDietState> {
);
}
}
final dailySchedule = <int, Map<int, Map<int, DailySchedule>>>{};
for (final (dayIndex, day) in state.dietData.dailySchedule!.indexed) {
final mappedDay = <int, Map<int, DailySchedule>>{};
for (final (mealIndex, meal) in day.indexed) {
final mappedMeal = <int, DailySchedule>{};
for (final (productIndex, product) in meal.indexed) {
mappedMeal[productIndex + 1] = product;
}
mappedDay[mealIndex + 1] = mappedMeal;
}
dailySchedule[dayIndex + 1] = mappedDay;
}
final diet = Diet(
id: 0,
name: state.dietData.name ?? '',
image: imageInBase64,
duration: state.dietData.duration ?? 0,
state: state.dietData.state ?? '',
state: state.dietData.state ?? DietState.active,
type: DietType(
meat: state.dietData.type['meat'],
veg: state.dietData.type['veg'],
@@ -249,7 +300,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
light: state.dietData.type['light'],
),
details: state.dietData.details ?? '',
dailySchedule: state.dietData.dailySchedule ?? [],
dailySchedule: dailySchedule,
author: Author(
authorSlug: authorProfile.slug,
authorName: authorProfile.name,
@@ -267,32 +318,32 @@ class CreateDietCubit extends Cubit<CreateDietState> {
return diet;
}
Future<String?> publishDiet({bool toDraft = false}) async {
Future<String?> publishDiet() async {
emit(state.copyWith(isDietPublishing: true));
if (!_isFromValid()) return null;
try {
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,
final dietState = state.dietData.state!;
final dailySchedule = state.dietData.dailySchedule;
await createDietRepository.createDiet(
name: state.dietData.name ?? '',
duration: state.dietData.duration ?? 7,
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,
details: state.dietData.details ?? '',
dailySchedule: dailySchedule ?? [],
);
final dailySchedule = state.dietData.dailySchedule;
createDietRepository.createDietDailySchedule(
id: diet.id,
model: {'daily_schedule': dailySchedule},
);
emit(state.copyWith(published: !toDraft, drafted: toDraft));
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
return state.dietData.name;
} catch (e, stackTrace) {
} on DioException catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
}
emit(state.copyWith(isDietPublishing: false));
return null;
}
@@ -304,7 +355,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
}
if (state.dietData.name == null || state.dietData.name == '') {
result = false;
emit(state.copyWith(dietNameError: 'Поле не заполненно'));
emit(state.copyWith(dietNameError: 'Поле не заполнено'));
}
if ((state.dietData.details?.length ?? 0) < 3) {
result = false;
@@ -312,7 +363,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
}
if (state.dietData.details == null || state.dietData.details == '') {
result = false;
emit(state.copyWith(dietDescriptionError: 'Поле не заполненно'));
emit(state.copyWith(dietDescriptionError: 'Поле не заполнено'));
}
if (state.dietData.duration == null) {
result = false;
@@ -336,15 +387,11 @@ class CreateDietCubit extends Cubit<CreateDietState> {
if (dailySchedule == null) {
result = false;
} else {
final dailyScheduleErrors = state.dailyScheduleErrors.map((element) {
return element.map((element) => element).toList();
}).toList();
final dailyScheduleErrors = [...state.dailyScheduleErrors];
for (final (dayIndex, day) in dailySchedule.indexed) {
for (final (mealIndex, meal) in day.indexed) {
if (meal.isEmpty) {
result = false;
dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты';
}
if (day.every((meal) => meal.isEmpty)) {
result = false;
dailyScheduleErrors[dayIndex] = 'Добавьте продукты';
}
}
emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
@@ -358,16 +405,13 @@ class CreateDietCubit extends Cubit<CreateDietState> {
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] = '';
}
final dailyScheduleErrors = [...state.dailyScheduleErrors];
final day = dailySchedule[dayIndex];
if (day.every((meal) => meal.isEmpty)) {
result = false;
dailyScheduleErrors[dayIndex] = 'Добавьте продукты';
} else {
dailyScheduleErrors[dayIndex] = '';
}
emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
}

View File

@@ -9,6 +9,7 @@ part 'create_diet_state.freezed.dart';
class CreateDietState with _$CreateDietState {
const factory CreateDietState({
required bool isLoading,
required bool isDietPublishing,
required String? errorMessage,
required CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
@@ -17,15 +18,17 @@ class CreateDietState with _$CreateDietState {
String? dietDurationError,
String? dietStateError,
String? dietImageError,
required List<List<String>> dailyScheduleErrors,
required List<String> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex,
@Default(false) bool published,
@Default(false) bool drafted,
@Default(false) bool isDietBeingEdited,
}) = _CreateDietState;
factory CreateDietState.initial() => CreateDietState(
dailyScheduleErrors: List.filled(21, ['', '', '', '', '']),
dailyScheduleErrors: List.filled(21, ''),
isDietPublishing: false,
isLoading: false,
errorMessage: null,
dietData: CreateDietModel(

View File

@@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$CreateDietState {
bool get isLoading => throw _privateConstructorUsedError;
bool get isDietPublishing => throw _privateConstructorUsedError;
String? get errorMessage => throw _privateConstructorUsedError;
CreateDietModel get dietData => throw _privateConstructorUsedError;
List<List<DailySchedule>>? get copiedDay =>
@@ -26,12 +27,12 @@ mixin _$CreateDietState {
String? get dietDurationError => throw _privateConstructorUsedError;
String? get dietStateError => throw _privateConstructorUsedError;
String? get dietImageError => throw _privateConstructorUsedError;
List<List<String>> get dailyScheduleErrors =>
throw _privateConstructorUsedError;
List<String> get dailyScheduleErrors => throw _privateConstructorUsedError;
int? get durationIndex => throw _privateConstructorUsedError;
int? get dietStateIndex => throw _privateConstructorUsedError;
bool get published => throw _privateConstructorUsedError;
bool get drafted => throw _privateConstructorUsedError;
bool get isDietBeingEdited => throw _privateConstructorUsedError;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -48,6 +49,7 @@ abstract class $CreateDietStateCopyWith<$Res> {
@useResult
$Res call(
{bool isLoading,
bool isDietPublishing,
String? errorMessage,
CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
@@ -56,11 +58,12 @@ abstract class $CreateDietStateCopyWith<$Res> {
String? dietDurationError,
String? dietStateError,
String? dietImageError,
List<List<String>> dailyScheduleErrors,
List<String> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex,
bool published,
bool drafted});
bool drafted,
bool isDietBeingEdited});
$CreateDietModelCopyWith<$Res> get dietData;
}
@@ -81,6 +84,7 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
@override
$Res call({
Object? isLoading = null,
Object? isDietPublishing = null,
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
@@ -94,12 +98,17 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
Object? dietStateIndex = freezed,
Object? published = null,
Object? drafted = null,
Object? isDietBeingEdited = null,
}) {
return _then(_value.copyWith(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
isDietPublishing: null == isDietPublishing
? _value.isDietPublishing
: isDietPublishing // ignore: cast_nullable_to_non_nullable
as bool,
errorMessage: freezed == errorMessage
? _value.errorMessage
: errorMessage // ignore: cast_nullable_to_non_nullable
@@ -135,7 +144,7 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
dailyScheduleErrors: null == dailyScheduleErrors
? _value.dailyScheduleErrors
: dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
as List<List<String>>,
as List<String>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -152,6 +161,10 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
? _value.drafted
: drafted // ignore: cast_nullable_to_non_nullable
as bool,
isDietBeingEdited: null == isDietBeingEdited
? _value.isDietBeingEdited
: isDietBeingEdited // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -176,6 +189,7 @@ abstract class _$$CreateDietStateImplCopyWith<$Res>
@useResult
$Res call(
{bool isLoading,
bool isDietPublishing,
String? errorMessage,
CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
@@ -184,11 +198,12 @@ abstract class _$$CreateDietStateImplCopyWith<$Res>
String? dietDurationError,
String? dietStateError,
String? dietImageError,
List<List<String>> dailyScheduleErrors,
List<String> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex,
bool published,
bool drafted});
bool drafted,
bool isDietBeingEdited});
@override
$CreateDietModelCopyWith<$Res> get dietData;
@@ -208,6 +223,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? isLoading = null,
Object? isDietPublishing = null,
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
@@ -221,12 +237,17 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
Object? dietStateIndex = freezed,
Object? published = null,
Object? drafted = null,
Object? isDietBeingEdited = null,
}) {
return _then(_$CreateDietStateImpl(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
isDietPublishing: null == isDietPublishing
? _value.isDietPublishing
: isDietPublishing // ignore: cast_nullable_to_non_nullable
as bool,
errorMessage: freezed == errorMessage
? _value.errorMessage
: errorMessage // ignore: cast_nullable_to_non_nullable
@@ -262,7 +283,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
dailyScheduleErrors: null == dailyScheduleErrors
? _value._dailyScheduleErrors
: dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
as List<List<String>>,
as List<String>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -279,6 +300,10 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
? _value.drafted
: drafted // ignore: cast_nullable_to_non_nullable
as bool,
isDietBeingEdited: null == isDietBeingEdited
? _value.isDietBeingEdited
: isDietBeingEdited // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -288,6 +313,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
class _$CreateDietStateImpl implements _CreateDietState {
const _$CreateDietStateImpl(
{required this.isLoading,
required this.isDietPublishing,
required this.errorMessage,
required this.dietData,
final List<List<DailySchedule>>? copiedDay,
@@ -296,17 +322,20 @@ class _$CreateDietStateImpl implements _CreateDietState {
this.dietDurationError,
this.dietStateError,
this.dietImageError,
required final List<List<String>> dailyScheduleErrors,
required final List<String> dailyScheduleErrors,
this.durationIndex,
this.dietStateIndex,
this.published = false,
this.drafted = false})
this.drafted = false,
this.isDietBeingEdited = false})
: _copiedDay = copiedDay,
_dailyScheduleErrors = dailyScheduleErrors;
@override
final bool isLoading;
@override
final bool isDietPublishing;
@override
final String? errorMessage;
@override
final CreateDietModel dietData;
@@ -330,9 +359,9 @@ class _$CreateDietStateImpl implements _CreateDietState {
final String? dietStateError;
@override
final String? dietImageError;
final List<List<String>> _dailyScheduleErrors;
final List<String> _dailyScheduleErrors;
@override
List<List<String>> get dailyScheduleErrors {
List<String> get dailyScheduleErrors {
if (_dailyScheduleErrors is EqualUnmodifiableListView)
return _dailyScheduleErrors;
// ignore: implicit_dynamic_type
@@ -349,10 +378,13 @@ class _$CreateDietStateImpl implements _CreateDietState {
@override
@JsonKey()
final bool drafted;
@override
@JsonKey()
final bool isDietBeingEdited;
@override
String toString() {
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)';
return 'CreateDietState(isLoading: $isLoading, isDietPublishing: $isDietPublishing, 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, isDietBeingEdited: $isDietBeingEdited)';
}
@override
@@ -362,6 +394,8 @@ class _$CreateDietStateImpl implements _CreateDietState {
other is _$CreateDietStateImpl &&
(identical(other.isLoading, isLoading) ||
other.isLoading == isLoading) &&
(identical(other.isDietPublishing, isDietPublishing) ||
other.isDietPublishing == isDietPublishing) &&
(identical(other.errorMessage, errorMessage) ||
other.errorMessage == errorMessage) &&
(identical(other.dietData, dietData) ||
@@ -386,13 +420,16 @@ class _$CreateDietStateImpl implements _CreateDietState {
other.dietStateIndex == dietStateIndex) &&
(identical(other.published, published) ||
other.published == published) &&
(identical(other.drafted, drafted) || other.drafted == drafted));
(identical(other.drafted, drafted) || other.drafted == drafted) &&
(identical(other.isDietBeingEdited, isDietBeingEdited) ||
other.isDietBeingEdited == isDietBeingEdited));
}
@override
int get hashCode => Object.hash(
runtimeType,
isLoading,
isDietPublishing,
errorMessage,
dietData,
const DeepCollectionEquality().hash(_copiedDay),
@@ -405,7 +442,8 @@ class _$CreateDietStateImpl implements _CreateDietState {
durationIndex,
dietStateIndex,
published,
drafted);
drafted,
isDietBeingEdited);
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -420,6 +458,7 @@ class _$CreateDietStateImpl implements _CreateDietState {
abstract class _CreateDietState implements CreateDietState {
const factory _CreateDietState(
{required final bool isLoading,
required final bool isDietPublishing,
required final String? errorMessage,
required final CreateDietModel dietData,
final List<List<DailySchedule>>? copiedDay,
@@ -428,15 +467,18 @@ abstract class _CreateDietState implements CreateDietState {
final String? dietDurationError,
final String? dietStateError,
final String? dietImageError,
required final List<List<String>> dailyScheduleErrors,
required final List<String> dailyScheduleErrors,
final int? durationIndex,
final int? dietStateIndex,
final bool published,
final bool drafted}) = _$CreateDietStateImpl;
final bool drafted,
final bool isDietBeingEdited}) = _$CreateDietStateImpl;
@override
bool get isLoading;
@override
bool get isDietPublishing;
@override
String? get errorMessage;
@override
CreateDietModel get dietData;
@@ -453,7 +495,7 @@ abstract class _CreateDietState implements CreateDietState {
@override
String? get dietImageError;
@override
List<List<String>> get dailyScheduleErrors;
List<String> get dailyScheduleErrors;
@override
int? get durationIndex;
@override
@@ -462,6 +504,8 @@ abstract class _CreateDietState implements CreateDietState {
bool get published;
@override
bool get drafted;
@override
bool get isDietBeingEdited;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -335,7 +335,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return _dayOfPlan(widget.diet.dailySchedule[index], index, s);
return _dayOfPlan(widget.diet.dailySchedule[index] ?? {}, index, s);
},
itemCount: widget.diet.dailySchedule.length,
),
@@ -363,7 +363,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
);
}
_dayOfPlan(List<List<DailySchedule>> day, int dayNumber, S s) {
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
_choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
@@ -431,8 +431,8 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s),
itemCount: day[widget.selectedIndexes[dayNumber]].length,
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s),
itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0,
),
SizedBox(height: 8),
],
@@ -450,7 +450,8 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
);
}
_daily(DailySchedule daily, S s) {
_daily(DailySchedule? daily, S s) {
if (daily == null) return SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(

View File

@@ -199,8 +199,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
final portion = DailySchedule(
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),
name: glossary.name,
uniq: '',
name: glossary.name,
productNutritionalValueId: glossary.id.toString(),
portion: measurementUnits,
);
@@ -240,7 +239,6 @@ class _AddMealWidgetState extends State<AddMealWidget> {
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),
name: newGlossary.name,
uniq: '',
productNutritionalValueId: newGlossary.id.toString(),
portion: measurementUnits,
);

View File

@@ -35,7 +35,7 @@ class _DayWidgetState extends State<DayWidget> {
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return MainBackgroundBouble(
hasError: state.dailyScheduleErrors[widget.index].where((element) => element.isNotEmpty).isNotEmpty,
hasError: state.dailyScheduleErrors[widget.index].isNotEmpty,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -216,16 +216,15 @@ class _DayWidgetState extends State<DayWidget> {
),
],
),
if (state.dailyScheduleErrors[widget.index][_selectedMealIndex].isNotEmpty) ...[
if (state.dailyScheduleErrors[widget.index].isNotEmpty) ...[
SizedBox(height: 12),
CustomErrorWidget(error: state.dailyScheduleErrors[widget.index][_selectedMealIndex]),
CustomErrorWidget(error: state.dailyScheduleErrors[widget.index]),
],
SizedBox(height: 20),
],
),
),
BlocBuilder<CreateDietCubit, CreateDietState>(
buildWhen: (previous, current) => previous.dietData.dailySchedule != current.dietData.dailySchedule,
builder: (context, state) {
if (state.dietData.dailySchedule != null) {
final day = state.dietData.dailySchedule?[widget.index];

View File

@@ -71,6 +71,8 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
final s = S.of(context);
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
final hasImage = state.dietData.image != null || state.dietData.imageUrl != null;
final hasFile = state.dietData.image != null;
return Column(
children: [
GestureDetector(
@@ -81,17 +83,26 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
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,
image: hasImage
? DecorationImage(
fit: BoxFit.cover,
image: (hasFile
? FileImage(
File(state.dietData.image?.path ?? ''),
)
: NetworkImage(state.dietData.imageUrl ?? '')) as ImageProvider,
)
: null,
color: appTheme.gray50,
),
width: MediaQuery.sizeOf(context).width,
padding: state.dietData.image != null ? null : EdgeInsets.all(8),
child: state.dietData.image != null
padding: hasImage ? null : EdgeInsets.all(8),
child: hasImage
? Padding(
padding: const EdgeInsets.only(top: 6, right: 6),
child: GestureDetector(
onTap: () {
context.read<CreateDietCubit>().setImage(null);
context.read<CreateDietCubit>().setImage(null, imageUrl: null);
},
child: Align(
alignment: Alignment.topRight,

View File

@@ -18,8 +18,7 @@ class DietTypeChoiceWidget extends StatefulWidget {
class _DietTypeChoiceWidgetState extends State<DietTypeChoiceWidget> {
final List<(String, String)> choiceContent = [];
@override
void didChangeDependencies() {
// const List<String> typeValues = ['veg', 'light', 'carb', 'meat'];
void didChangeDependencies() {
super.didChangeDependencies();
final s = S.of(context);
if (choiceContent.isEmpty) {

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_c
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/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -18,14 +19,23 @@ class StatusDropDownWidget extends StatefulWidget {
class _StatusDropDownWidgetState extends State<StatusDropDownWidget> {
final List<String> dietStateTitles = [];
late final List<DietState> dietStates = DietState.values;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final s = S.of(context);
if (dietStateTitles.isEmpty) {
dietStateTitles.add(s.active_diet_state);
dietStateTitles.add(s.personal_diet_state);
dietStates.forEach((elem) {
switch (elem) {
case DietState.active:
dietStateTitles.add(s.active_diet_state);
case DietState.draft:
dietStateTitles.add(s.draft);
case DietState.personal:
dietStateTitles.add(s.personal_diet_state);
}
});
}
}
@@ -45,7 +55,8 @@ class _StatusDropDownWidgetState extends State<StatusDropDownWidget> {
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDietState(dietStateTitles.indexOf(value));
final index = dietStates.indexOf(value);
context.read<CreateDietCubit>().setDietState(value, index);
}
},
items: dietStateTitles
@@ -55,11 +66,11 @@ class _StatusDropDownWidgetState extends State<StatusDropDownWidget> {
dietStateTitles[index],
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
value: dietStateTitles[index],
value: dietStates[index],
),
)
.toList(),
value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0],
value: state.dietStateIndex == null ? null : dietStates[state.dietStateIndex ?? 0],
),
if (state.dietStateError != null) ...[
SizedBox(

View File

@@ -1,5 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
@@ -8,40 +8,54 @@ class CreateDietRepository {
CreateDietRepository({required this.createDietService});
Future<CreatedDiet> createDiet({
String? name,
int? duration,
String? state,
String? meatType,
String? carbType,
String? vegType,
String? lightType,
String? details,
File? image,
required String name,
required int duration,
required DietState state,
required String meatType,
required String carbType,
required String vegType,
required String lightType,
required String details,
required File image,
required List<List<List<DailySchedule>>> dailySchedule,
}) {
return createDietService.createDiet(
name: name,
duration: duration,
state: state,
meatType: meatType,
carbType: carbType,
vegType: vegType,
lightType: lightType,
details: details,
image: image,
);
}
final dietMap = <String, dynamic>{};
for (final (dayIndex, day) in dailySchedule.indexed) {
for (final (mealIndex, meal) in day.indexed) {
for (final (productIndex, product) in meal.indexed) {
final jsonProduct = product.toJson();
dietMap.addAll({
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][name]': jsonProduct['name'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][portion]': jsonProduct['portion'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][quantity]': jsonProduct['quantity'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][product_nutritional_value_id]': jsonProduct['product_nutritional_value_id'],
});
}
}
}
Future<void> createDietDailySchedule({
required int id,
required Map<String, dynamic> model,
}) {
try {
return createDietService.createDietDailySchedule(
id: id,
model: model,
);
} catch (e) {
rethrow;
dietMap.addAll({
'name': name,
'details': details,
'duration': duration,
'type[meat]': meatType,
'type[carb]': carbType,
'type[veg]': vegType,
'type[light]': lightType,
'image': MultipartFile.fromFileSync(
image.path,
filename: image.path.split(Platform.pathSeparator).last,
),
});
final formData = FormData.fromMap(dietMap);
switch (state) {
case DietState.active:
return createDietService.createPublishedDiet(formData: formData);
case DietState.draft:
return createDietService.createDraftedDiet(formData: formData);
case DietState.personal:
return createDietService.createPersonalDiet(formData: formData);
}
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:retrofit/retrofit.dart';
@@ -10,23 +8,21 @@ part 'create_diet_service.g.dart';
abstract class CreateDietService {
factory CreateDietService(Dio dio, {String baseUrl}) = _CreateDietService;
@POST('/api/diets/create')
@POST('/api/diets/create/published')
@MultiPart()
Future<CreatedDiet> createDiet({
@Part(name: 'name') String? name,
@Part(name: 'duration') int? duration,
@Part(name: 'state') String? state,
@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,
Future<CreatedDiet> createPublishedDiet({
@Body() required FormData formData,
});
@POST('/api/diets/create/{id}/daily_schedule')
Future<void> createDietDailySchedule({
@Path() required int id,
@Body() required Map<String, dynamic> model,
@POST('/api/diets/create/drafted')
@MultiPart()
Future<CreatedDiet> createDraftedDiet({
@Body() required FormData formData,
});
@POST('/api/diets/create/personalized')
@MultiPart()
Future<CreatedDiet> createPersonalDiet({
@Body() required FormData formData,
});
}

View File

@@ -18,59 +18,11 @@ class _CreateDietService implements CreateDietService {
final ParseErrorLogger? errorLogger;
@override
Future<CreatedDiet> createDiet({
String? name,
int? duration,
String? state,
String? meatType,
String? carbType,
String? vegType,
String? lightType,
String? details,
File? image,
}) async {
Future<CreatedDiet> createPublishedDiet({required FormData formData}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
if (name != null) {
_data.fields.add(MapEntry('name', name));
}
if (duration != null) {
_data.fields.add(MapEntry('duration', duration.toString()));
}
if (state != null) {
_data.fields.add(MapEntry('state', state));
}
if (meatType != null) {
_data.fields.add(MapEntry('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) {
_data.fields.add(MapEntry('details', details));
}
if (image != null) {
if (image != null) {
_data.files.add(
MapEntry(
'image',
MultipartFile.fromFileSync(
image.path,
filename: image.path.split(Platform.pathSeparator).last,
),
),
);
}
}
final _data = formData;
final _options = _setStreamType<CreatedDiet>(
Options(
method: 'POST',
@@ -80,7 +32,7 @@ class _CreateDietService implements CreateDietService {
)
.compose(
_dio.options,
'/api/diets/create',
'/api/diets/create/published',
queryParameters: queryParameters,
data: _data,
)
@@ -98,26 +50,67 @@ class _CreateDietService implements CreateDietService {
}
@override
Future<void> createDietDailySchedule({
required int id,
required Map<String, dynamic> model,
}) async {
Future<CreatedDiet> createDraftedDiet({required FormData formData}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(model);
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
final _data = formData;
final _options = _setStreamType<CreatedDiet>(
Options(
method: 'POST',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/api/diets/create/${id}/daily_schedule',
'/api/diets/create/drafted',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late CreatedDiet _value;
try {
_value = CreatedDiet.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
@override
Future<CreatedDiet> createPersonalDiet({required FormData formData}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = formData;
final _options = _setStreamType<CreatedDiet>(
Options(
method: 'POST',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/api/diets/create/personalized',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late CreatedDiet _value;
try {
_value = CreatedDiet.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {

View File

@@ -25,7 +25,25 @@ class DevToolsScreenContent extends StatefulWidget {
class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
String baseUrlForTest = '';
final List<TestCases> testCases = [Test850task(), GetChatRoomPreviews(), GetChatRoom(), SendMessage(), GetChatMessages(), GetChatRoomIdByUserSlug()];
final List<TestCases> testCases = [
Test850task(),
GetChatRoomPreviews(),
GetChatRoom(),
SendMessage(),
GetChatMessages(),
GetChatRoomIdByUserSlug(),
GetBlogPostList(),
GetRecipes(),
GetDiets(),
GetPlans(),
GetBlogPostComments(),
SendBlogPostComment(),
GetDietsComments(),
SendDietsComment(),
GetPlansComments(),
SendPlansComment(),
GetRecipesComments(),
];
final controller = TextEditingController();
late final List<TestState> testsStates;
@@ -41,7 +59,6 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
@@ -74,76 +91,89 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
),
],
),
SizedBox(
const SizedBox(
height: 10,
),
if (baseUrlForTest != baseUrl)
CustomOutlinedButton(
text: 'Set dev2 base url',
onPressed: () {
setState(() {
this.baseUrlForTest = baseUrl;
});
},
buttonStyle: ButtonStyle(),
),
SizedBox(
height: baseUrlForTest != baseUrl ? 40 : 10,
),
if (baseUrlForTest != 'https://eatvio.ru')
ListView.builder(
shrinkWrap: true,
itemCount: testCases.length,
itemBuilder: (context, index) => Row(
children: [
Text('${index + 1}. ${testCases[index].name}'),
SizedBox(
width: 50,
),
Expanded(
child: CustomOutlinedButton(
text: 'run',
height: 20,
width: 60,
onPressed: () async {
if (testsStates[index] == TestState.loading) return;
setState(() {
testsStates[index] = TestState.loading;
});
final result = await testCases[index].run(baseUrl: baseUrlForTest);
if (result) {
setState(() {
testsStates[index] = TestState.success;
});
showSuccessToast('${testCases[index].name} passed');
} else {
setState(() {
testsStates[index] = TestState.fail;
});
showErrorToast('${testCases[index].name} failed');
}
},
buttonStyle: ButtonStyle(),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text('${index + 1}. ${testCases[index].name}'),
),
),
SizedBox(
width: 30,
),
switch (testsStates[index]) {
TestState.success => Expanded(
child: Container(
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomOutlinedButton(
text: 'run',
height: 20,
color: Colors.green,
width: 60,
onPressed: () async {
if (testsStates[index] == TestState.loading) return;
setState(() {
testsStates[index] = TestState.loading;
});
final result = await testCases[index].run(baseUrl: baseUrlForTest);
if (result) {
setState(() {
testsStates[index] = TestState.success;
});
showSuccessToast('${testCases[index].name} passed');
} else {
setState(() {
testsStates[index] = TestState.fail;
});
showErrorToast('${testCases[index].name} failed');
}
},
buttonStyle: ButtonStyle(),
),
),
TestState.fail => Expanded(
child: Container(
height: 20,
color: Colors.red,
),
),
TestState.notRun => Expanded(
child: Container(
height: 20,
color: Colors.grey,
),
),
TestState.loading => Expanded(
child: Container(
height: 20,
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
),
),
},
],
SizedBox(width: 5),
switch (testsStates[index]) {
TestState.success => Container(
width: 20,
height: 20,
color: Colors.green,
),
TestState.fail => Container(
width: 20,
height: 20,
color: Colors.red,
),
TestState.notRun => Container(
width: 20,
height: 20,
color: Colors.grey,
),
TestState.loading => Container(
width: 20,
height: 20,
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
),
},
],
),
],
),
),
),
],

View File

@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
sealed class TestCases {
String get name => throw UnimplementedError();
@@ -115,6 +116,7 @@ class Test850task implements TestCases {
}
}
// ChatService
class GetChatRoomPreviews implements TestCases {
@override
String get name => '/api/chat/index';
@@ -140,7 +142,7 @@ class GetChatRoom implements TestCases {
@override
Future<bool> run({required String baseUrl}) async {
try {
try {
final dio = sl<Dio>();
final chatService = ChatService(dio, baseUrl: baseUrl);
@@ -159,7 +161,7 @@ class SendMessage implements TestCases {
@override
Future<bool> run({required String baseUrl}) async {
try {
try {
final dio = sl<Dio>();
final chatService = ChatService(dio, baseUrl: baseUrl);
@@ -178,7 +180,7 @@ class GetChatMessages implements TestCases {
@override
Future<bool> run({required String baseUrl}) async {
try {
try {
final dio = sl<Dio>();
final chatService = ChatService(dio, baseUrl: baseUrl);
@@ -197,7 +199,7 @@ class GetChatRoomIdByUserSlug implements TestCases {
@override
Future<bool> run({required String baseUrl}) async {
try {
try {
final dio = sl<Dio>();
final chatService = ChatService(dio, baseUrl: baseUrl);
final consultantService = FeedService(dio, baseUrl: baseUrl);
@@ -217,3 +219,230 @@ class GetChatRoomIdByUserSlug implements TestCases {
}
}
}
// FeedService
class GetBlogPostList implements TestCases {
@override
String get name => '/api/blogPosts/index';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
await feedService.getBlogPostList(page: 1);
return true;
} catch (e) {
return false;
}
}
}
class GetRecipes implements TestCases {
@override
String get name => '/api/recipes/index';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final recipeService = RecipeService(dio, baseUrl: baseUrl);
final tagsModel = await recipeService.getTags();
final tags = tagsModel.data;
final tagsIds = tags.map((e) => e.id).toList();
await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
return true;
} catch (e) {
return false;
}
}
}
class GetDiets implements TestCases {
@override
String get name => '/api/diets/index';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
await feedService.getDiets(page: 1, query: null);
return true;
} catch (e) {
return false;
}
}
}
class GetPlans implements TestCases {
@override
String get name => '/api/plans/index';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
await feedService.getPlans(page: 1, query: null);
return true;
} catch (e) {
return false;
}
}
}
class GetBlogPostComments implements TestCases {
@override
String get name => '/api/blogPosts/{id}/comments/show';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
final blogPosts = blogPostsModel.feedBlogPostList;
await feedService.getBlogPostComments(
id: blogPosts.first.id.toString(),
);
return true;
} catch (e) {
return false;
}
}
}
class SendBlogPostComment implements TestCases {
@override
String get name => '/api/blogPosts/{id}/comment/create';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
final blogPosts = blogPostsModel.feedBlogPostList;
await feedService.sendBlogPostComment(
id: blogPosts.first.id.toString(),
body: 'auto test message',
);
return true;
} catch (e) {
return false;
}
}
}
class GetDietsComments implements TestCases {
@override
String get name => '/api/diets/{id}/comments/show';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final dietsModel = await feedService.getDiets(page: 1, query: null);
final diets = dietsModel.data;
await feedService.getDietsComments(
id: diets.first.id.toString(),
);
return true;
} catch (e) {
return false;
}
}
}
class SendDietsComment implements TestCases {
@override
String get name => '/api/diets/{id}/comment/create';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final dietsModel = await feedService.getDiets(page: 1, query: null);
final diets = dietsModel.data;
await feedService.sendDietsComment(
id: diets.first.id.toString(),
body: 'auto test message',
);
return true;
} catch (e) {
return false;
}
}
}
class GetPlansComments implements TestCases {
@override
String get name => '/api/plans/{slug}/comments/show';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final plansModel = await feedService.getPlans(page: 1, query: null);
final plans = plansModel.data;
await feedService.getPlansComments(
slug: plans.first.slug,
);
return true;
} catch (e) {
return false;
}
}
}
class SendPlansComment implements TestCases {
@override
String get name => '/api/plans/{slug}/comment/create';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final plansModel = await feedService.getPlans(page: 1, query: null);
final plans = plansModel.data;
await feedService.sendPlansComment(
slug: plans.first.slug,
body: 'auto test message',
);
return true;
} catch (e) {
return false;
}
}
}
class GetRecipesComments implements TestCases {
@override
String get name => '/api/recipes/{id}/comments/show';
@override
Future<bool> run({required String baseUrl}) async {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final recipeService = RecipeService(dio, baseUrl: baseUrl);
final tagsModel = await recipeService.getTags();
final tags = tagsModel.data;
final tagsIds = tags.map((e) => e.id).toList();
final recipesModel = await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
final recipes = recipesModel.recipesList;
await feedService.getRecipesComments(
id: recipes.first.id.toString(),
);
return true;
} catch (e) {
return false;
}
}
}

View File

@@ -454,7 +454,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return _dayOfPlan(widget.diet.dailySchedule[index], index, s);
return _dayOfPlan(widget.diet.dailySchedule[index + 1] ?? {}, index, s);
},
itemCount: widget.diet.dailySchedule.length,
),
@@ -500,7 +500,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
);
}
_dayOfPlan(List<List<DailySchedule>> day, int dayNumber, S s) {
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
_choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
@@ -568,8 +568,8 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s),
itemCount: day[widget.selectedIndexes[dayNumber]].length,
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s),
itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0,
),
SizedBox(height: 8),
],
@@ -587,7 +587,8 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
);
}
_daily(DailySchedule daily, S s) {
_daily(DailySchedule? daily, S s) {
if (daily == null) return const SizedBox();
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(

View File

@@ -23,10 +23,10 @@ class Diet with _$Diet {
@JsonKey(name: "name") required String name,
@JsonKey(name: "image") required String image,
@JsonKey(name: "duration") required int duration,
@JsonKey(name: "state") required String state,
@JsonKey(name: "state") required DietState state,
@JsonKey(name: "type") required DietType type,
@JsonKey(name: "details") required String details,
@JsonKey(name: "daily_schedule") required List<List<List<DailySchedule>>> dailySchedule,
@JsonKey(name: "daily_schedule") required Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
@JsonKey(name: "author") required Author author,
@JsonKey(name: "total_likes") required int totalLikes,
@JsonKey(name: "total_views") required int totalViews,
@@ -39,10 +39,30 @@ class Diet with _$Diet {
factory Diet.fromJson(Map<String, dynamic> json) => _$DietFromJson(json);
}
enum DietState {
@JsonKey(name: 'active')
active,
@JsonKey(name: 'draft')
draft,
@JsonKey(name: 'personal')
personal;
String toString() {
switch (this) {
case DietState.active:
return 'active';
case DietState.draft:
return 'draft';
case DietState.personal:
return 'personal';
}
}
}
@freezed
class CreatedDiet with _$CreatedDiet {
const factory CreatedDiet({
@JsonKey(name: "id") required int id,
@JsonKey(name: "id") required int id,
}) = _CreatedDiet;
factory CreatedDiet.fromJson(Map<String, dynamic> json) => _$CreatedDietFromJson(json);
@@ -51,9 +71,9 @@ class CreatedDiet with _$CreatedDiet {
int parseDuration(dynamic duration) {
if (duration is String) {
return int.parse(duration);
}else {
} else {
return duration;
}
}
}
@freezed
@@ -74,9 +94,8 @@ class Author with _$Author {
class DailySchedule with _$DailySchedule {
const factory DailySchedule({
@JsonKey(name: "name") required String name,
@JsonKey(name: "uniq", includeToJson: false) required String uniq,
@JsonKey(name: "portion") required Portion portion,
@JsonKey(name: "quantity") required int quantity,
@JsonKey(name: "quantity", fromJson: _parseQuantity) required int quantity,
@JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition,
@JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: false, includeIfNull: false) String? productNutritionalValueId,
}) = _DailySchedule;
@@ -84,8 +103,16 @@ class DailySchedule with _$DailySchedule {
factory DailySchedule.fromJson(Map<String, dynamic> json) => _$DailyScheduleFromJson(json);
}
int _parseQuantity(dynamic quantity) {
if (quantity is String) {
return int.parse(quantity);
} else {
return quantity;
}
}
enum Portion {
@JsonValue("grams")
@JsonValue("gr")
GRAMS,
@JsonValue("milliliters")
MILLILITERS,

View File

@@ -268,13 +268,13 @@ mixin _$Diet {
@JsonKey(name: "duration")
int get duration => throw _privateConstructorUsedError;
@JsonKey(name: "state")
String get state => throw _privateConstructorUsedError;
DietState get state => throw _privateConstructorUsedError;
@JsonKey(name: "type")
DietType get type => throw _privateConstructorUsedError;
@JsonKey(name: "details")
String get details => throw _privateConstructorUsedError;
@JsonKey(name: "daily_schedule")
List<List<List<DailySchedule>>> get dailySchedule =>
Map<int, Map<int, Map<int, DailySchedule>>> get dailySchedule =>
throw _privateConstructorUsedError;
@JsonKey(name: "author")
Author get author => throw _privateConstructorUsedError;
@@ -310,11 +310,11 @@ abstract class $DietCopyWith<$Res> {
@JsonKey(name: "name") String name,
@JsonKey(name: "image") String image,
@JsonKey(name: "duration") int duration,
@JsonKey(name: "state") String state,
@JsonKey(name: "state") DietState state,
@JsonKey(name: "type") DietType type,
@JsonKey(name: "details") String details,
@JsonKey(name: "daily_schedule")
List<List<List<DailySchedule>>> dailySchedule,
Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
@JsonKey(name: "author") Author author,
@JsonKey(name: "total_likes") int totalLikes,
@JsonKey(name: "total_views") int totalViews,
@@ -378,7 +378,7 @@ class _$DietCopyWithImpl<$Res, $Val extends Diet>
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String,
as DietState,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
@@ -390,7 +390,7 @@ class _$DietCopyWithImpl<$Res, $Val extends Diet>
dailySchedule: null == dailySchedule
? _value.dailySchedule
: dailySchedule // ignore: cast_nullable_to_non_nullable
as List<List<List<DailySchedule>>>,
as Map<int, Map<int, Map<int, DailySchedule>>>,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
@@ -455,11 +455,11 @@ abstract class _$$DietImplCopyWith<$Res> implements $DietCopyWith<$Res> {
@JsonKey(name: "name") String name,
@JsonKey(name: "image") String image,
@JsonKey(name: "duration") int duration,
@JsonKey(name: "state") String state,
@JsonKey(name: "state") DietState state,
@JsonKey(name: "type") DietType type,
@JsonKey(name: "details") String details,
@JsonKey(name: "daily_schedule")
List<List<List<DailySchedule>>> dailySchedule,
Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
@JsonKey(name: "author") Author author,
@JsonKey(name: "total_likes") int totalLikes,
@JsonKey(name: "total_views") int totalViews,
@@ -522,7 +522,7 @@ class __$$DietImplCopyWithImpl<$Res>
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String,
as DietState,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
@@ -534,7 +534,7 @@ class __$$DietImplCopyWithImpl<$Res>
dailySchedule: null == dailySchedule
? _value._dailySchedule
: dailySchedule // ignore: cast_nullable_to_non_nullable
as List<List<List<DailySchedule>>>,
as Map<int, Map<int, Map<int, DailySchedule>>>,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
@@ -579,7 +579,7 @@ class _$DietImpl implements _Diet {
@JsonKey(name: "type") required this.type,
@JsonKey(name: "details") required this.details,
@JsonKey(name: "daily_schedule")
required final List<List<List<DailySchedule>>> dailySchedule,
required final Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
@JsonKey(name: "author") required this.author,
@JsonKey(name: "total_likes") required this.totalLikes,
@JsonKey(name: "total_views") required this.totalViews,
@@ -608,20 +608,20 @@ class _$DietImpl implements _Diet {
final int duration;
@override
@JsonKey(name: "state")
final String state;
final DietState state;
@override
@JsonKey(name: "type")
final DietType type;
@override
@JsonKey(name: "details")
final String details;
final List<List<List<DailySchedule>>> _dailySchedule;
final Map<int, Map<int, Map<int, DailySchedule>>> _dailySchedule;
@override
@JsonKey(name: "daily_schedule")
List<List<List<DailySchedule>>> get dailySchedule {
if (_dailySchedule is EqualUnmodifiableListView) return _dailySchedule;
Map<int, Map<int, Map<int, DailySchedule>>> get dailySchedule {
if (_dailySchedule is EqualUnmodifiableMapView) return _dailySchedule;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_dailySchedule);
return EqualUnmodifiableMapView(_dailySchedule);
}
@override
@@ -728,11 +728,11 @@ abstract class _Diet implements Diet {
@JsonKey(name: "name") required final String name,
@JsonKey(name: "image") required final String image,
@JsonKey(name: "duration") required final int duration,
@JsonKey(name: "state") required final String state,
@JsonKey(name: "state") required final DietState state,
@JsonKey(name: "type") required final DietType type,
@JsonKey(name: "details") required final String details,
@JsonKey(name: "daily_schedule")
required final List<List<List<DailySchedule>>> dailySchedule,
required final Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
@JsonKey(name: "author") required final Author author,
@JsonKey(name: "total_likes") required final int totalLikes,
@JsonKey(name: "total_views") required final int totalViews,
@@ -758,7 +758,7 @@ abstract class _Diet implements Diet {
int get duration;
@override
@JsonKey(name: "state")
String get state;
DietState get state;
@override
@JsonKey(name: "type")
DietType get type;
@@ -767,7 +767,7 @@ abstract class _Diet implements Diet {
String get details;
@override
@JsonKey(name: "daily_schedule")
List<List<List<DailySchedule>>> get dailySchedule;
Map<int, Map<int, Map<int, DailySchedule>>> get dailySchedule;
@override
@JsonKey(name: "author")
Author get author;
@@ -1242,11 +1242,9 @@ DailySchedule _$DailyScheduleFromJson(Map<String, dynamic> json) {
mixin _$DailySchedule {
@JsonKey(name: "name")
String get name => throw _privateConstructorUsedError;
@JsonKey(name: "uniq", includeToJson: false)
String get uniq => throw _privateConstructorUsedError;
@JsonKey(name: "portion")
Portion get portion => throw _privateConstructorUsedError;
@JsonKey(name: "quantity")
@JsonKey(name: "quantity", fromJson: _parseQuantity)
int get quantity => throw _privateConstructorUsedError;
@JsonKey(includeToJson: false, includeFromJson: false)
DailyNutrition? get dailyNutrition => throw _privateConstructorUsedError;
@@ -1275,9 +1273,8 @@ abstract class $DailyScheduleCopyWith<$Res> {
@useResult
$Res call(
{@JsonKey(name: "name") String name,
@JsonKey(name: "uniq", includeToJson: false) String uniq,
@JsonKey(name: "portion") Portion portion,
@JsonKey(name: "quantity") int quantity,
@JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
DailyNutrition? dailyNutrition,
@JsonKey(
@@ -1306,7 +1303,6 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule>
@override
$Res call({
Object? name = null,
Object? uniq = null,
Object? portion = null,
Object? quantity = null,
Object? dailyNutrition = freezed,
@@ -1317,10 +1313,6 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
uniq: null == uniq
? _value.uniq
: uniq // ignore: cast_nullable_to_non_nullable
as String,
portion: null == portion
? _value.portion
: portion // ignore: cast_nullable_to_non_nullable
@@ -1365,9 +1357,8 @@ abstract class _$$DailyScheduleImplCopyWith<$Res>
@useResult
$Res call(
{@JsonKey(name: "name") String name,
@JsonKey(name: "uniq", includeToJson: false) String uniq,
@JsonKey(name: "portion") Portion portion,
@JsonKey(name: "quantity") int quantity,
@JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
DailyNutrition? dailyNutrition,
@JsonKey(
@@ -1395,7 +1386,6 @@ class __$$DailyScheduleImplCopyWithImpl<$Res>
@override
$Res call({
Object? name = null,
Object? uniq = null,
Object? portion = null,
Object? quantity = null,
Object? dailyNutrition = freezed,
@@ -1406,10 +1396,6 @@ class __$$DailyScheduleImplCopyWithImpl<$Res>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
uniq: null == uniq
? _value.uniq
: uniq // ignore: cast_nullable_to_non_nullable
as String,
portion: null == portion
? _value.portion
: portion // ignore: cast_nullable_to_non_nullable
@@ -1435,9 +1421,9 @@ class __$$DailyScheduleImplCopyWithImpl<$Res>
class _$DailyScheduleImpl implements _DailySchedule {
const _$DailyScheduleImpl(
{@JsonKey(name: "name") required this.name,
@JsonKey(name: "uniq", includeToJson: false) required this.uniq,
@JsonKey(name: "portion") required this.portion,
@JsonKey(name: "quantity") required this.quantity,
@JsonKey(name: "quantity", fromJson: _parseQuantity)
required this.quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
this.dailyNutrition,
@JsonKey(
@@ -1454,13 +1440,10 @@ class _$DailyScheduleImpl implements _DailySchedule {
@JsonKey(name: "name")
final String name;
@override
@JsonKey(name: "uniq", includeToJson: false)
final String uniq;
@override
@JsonKey(name: "portion")
final Portion portion;
@override
@JsonKey(name: "quantity")
@JsonKey(name: "quantity", fromJson: _parseQuantity)
final int quantity;
@override
@JsonKey(includeToJson: false, includeFromJson: false)
@@ -1475,7 +1458,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
@override
String toString() {
return 'DailySchedule(name: $name, uniq: $uniq, portion: $portion, quantity: $quantity, dailyNutrition: $dailyNutrition, productNutritionalValueId: $productNutritionalValueId)';
return 'DailySchedule(name: $name, portion: $portion, quantity: $quantity, dailyNutrition: $dailyNutrition, productNutritionalValueId: $productNutritionalValueId)';
}
@override
@@ -1484,7 +1467,6 @@ class _$DailyScheduleImpl implements _DailySchedule {
(other.runtimeType == runtimeType &&
other is _$DailyScheduleImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.uniq, uniq) || other.uniq == uniq) &&
(identical(other.portion, portion) || other.portion == portion) &&
(identical(other.quantity, quantity) ||
other.quantity == quantity) &&
@@ -1497,7 +1479,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, name, uniq, portion, quantity,
int get hashCode => Object.hash(runtimeType, name, portion, quantity,
dailyNutrition, productNutritionalValueId);
/// Create a copy of DailySchedule
@@ -1519,9 +1501,9 @@ class _$DailyScheduleImpl implements _DailySchedule {
abstract class _DailySchedule implements DailySchedule {
const factory _DailySchedule(
{@JsonKey(name: "name") required final String name,
@JsonKey(name: "uniq", includeToJson: false) required final String uniq,
@JsonKey(name: "portion") required final Portion portion,
@JsonKey(name: "quantity") required final int quantity,
@JsonKey(name: "quantity", fromJson: _parseQuantity)
required final int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
final DailyNutrition? dailyNutrition,
@JsonKey(
@@ -1538,13 +1520,10 @@ abstract class _DailySchedule implements DailySchedule {
@JsonKey(name: "name")
String get name;
@override
@JsonKey(name: "uniq", includeToJson: false)
String get uniq;
@override
@JsonKey(name: "portion")
Portion get portion;
@override
@JsonKey(name: "quantity")
@JsonKey(name: "quantity", fromJson: _parseQuantity)
int get quantity;
@override
@JsonKey(includeToJson: false, includeFromJson: false)

View File

@@ -29,16 +29,21 @@ _$DietImpl _$$DietImplFromJson(Map<String, dynamic> json) => _$DietImpl(
name: json['name'] as String,
image: json['image'] as String,
duration: (json['duration'] as num).toInt(),
state: json['state'] as String,
state: $enumDecode(_$DietStateEnumMap, json['state']),
type: DietType.fromJson(json['type'] as Map<String, dynamic>),
details: json['details'] as String,
dailySchedule: (json['daily_schedule'] as List<dynamic>)
.map((e) => (e as List<dynamic>)
.map((e) => (e as List<dynamic>)
.map((e) => DailySchedule.fromJson(e as Map<String, dynamic>))
.toList())
.toList())
.toList(),
dailySchedule: (json['daily_schedule'] as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(int.parse(k),
DailySchedule.fromJson(e as Map<String, dynamic>)),
)),
)),
),
author: Author.fromJson(json['author'] as Map<String, dynamic>),
totalLikes: (json['total_likes'] as num).toInt(),
totalViews: (json['total_views'] as num).toInt(),
@@ -56,10 +61,13 @@ Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) =>
'name': instance.name,
'image': instance.image,
'duration': instance.duration,
'state': instance.state,
'state': _$DietStateEnumMap[instance.state]!,
'type': instance.type,
'details': instance.details,
'daily_schedule': instance.dailySchedule,
'daily_schedule': instance.dailySchedule.map((k, e) => MapEntry(
k.toString(),
e.map((k, e) => MapEntry(
k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))),
'author': instance.author,
'total_likes': instance.totalLikes,
'total_views': instance.totalViews,
@@ -69,6 +77,12 @@ Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) =>
'daily_nutrition': instance.dailyNutrition,
};
const _$DietStateEnumMap = {
DietState.active: 'active',
DietState.draft: 'draft',
DietState.personal: 'personal',
};
_$CreatedDietImpl _$$CreatedDietImplFromJson(Map<String, dynamic> json) =>
_$CreatedDietImpl(
id: (json['id'] as num).toInt(),
@@ -101,9 +115,8 @@ Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) =>
_$DailyScheduleImpl(
name: json['name'] as String,
uniq: json['uniq'] as String,
portion: $enumDecode(_$PortionEnumMap, json['portion']),
quantity: (json['quantity'] as num).toInt(),
quantity: _parseQuantity(json['quantity']),
);
Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) =>
@@ -116,7 +129,7 @@ Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) =>
};
const _$PortionEnumMap = {
Portion.GRAMS: 'grams',
Portion.GRAMS: 'gr',
Portion.MILLILITERS: 'milliliters',
Portion.PCS: 'pcs',
};

View File

@@ -11,14 +11,14 @@ class Recipes with _$Recipes {
required String image,
required String state,
@JsonKey(name: 'cooking_time') required int cookingTime,
required List<Ingredient> ingredients,
required List<Ingredient>? ingredients,
required String description,
required List<Step> steps,
@JsonKey(name: 'video_url') required String videoUrl,
required List<Step>? steps,
@JsonKey(name: 'video_url') required String? videoUrl,
@JsonKey(name: 'created_at') required DateTime createdAt,
@JsonKey(name: 'updated_at') required DateTime updatedAt,
required String title,
required String type,
required String? title,
required String? type,
required String slug,
@JsonKey(name: 'total_comments') required int commentsCount,
@JsonKey(name: 'was_liked_by_me') required bool wasLikedByMe,

View File

@@ -25,17 +25,17 @@ mixin _$Recipes {
String get state => throw _privateConstructorUsedError;
@JsonKey(name: 'cooking_time')
int get cookingTime => throw _privateConstructorUsedError;
List<Ingredient> get ingredients => throw _privateConstructorUsedError;
List<Ingredient>? get ingredients => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
List<Step> get steps => throw _privateConstructorUsedError;
List<Step>? get steps => throw _privateConstructorUsedError;
@JsonKey(name: 'video_url')
String get videoUrl => throw _privateConstructorUsedError;
String? get videoUrl => throw _privateConstructorUsedError;
@JsonKey(name: 'created_at')
DateTime get createdAt => throw _privateConstructorUsedError;
@JsonKey(name: 'updated_at')
DateTime get updatedAt => throw _privateConstructorUsedError;
String get title => throw _privateConstructorUsedError;
String get type => throw _privateConstructorUsedError;
String? get title => throw _privateConstructorUsedError;
String? get type => throw _privateConstructorUsedError;
String get slug => throw _privateConstructorUsedError;
@JsonKey(name: 'total_comments')
int get commentsCount => throw _privateConstructorUsedError;
@@ -68,14 +68,14 @@ abstract class $RecipesCopyWith<$Res> {
String image,
String state,
@JsonKey(name: 'cooking_time') int cookingTime,
List<Ingredient> ingredients,
List<Ingredient>? ingredients,
String description,
List<Step> steps,
@JsonKey(name: 'video_url') String videoUrl,
List<Step>? steps,
@JsonKey(name: 'video_url') String? videoUrl,
@JsonKey(name: 'created_at') DateTime createdAt,
@JsonKey(name: 'updated_at') DateTime updatedAt,
String title,
String type,
String? title,
String? type,
String slug,
@JsonKey(name: 'total_comments') int commentsCount,
@JsonKey(name: 'was_liked_by_me') bool wasLikedByMe,
@@ -106,14 +106,14 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
Object? image = null,
Object? state = null,
Object? cookingTime = null,
Object? ingredients = null,
Object? ingredients = freezed,
Object? description = null,
Object? steps = null,
Object? videoUrl = null,
Object? steps = freezed,
Object? videoUrl = freezed,
Object? createdAt = null,
Object? updatedAt = null,
Object? title = null,
Object? type = null,
Object? title = freezed,
Object? type = freezed,
Object? slug = null,
Object? commentsCount = null,
Object? wasLikedByMe = null,
@@ -139,22 +139,22 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
? _value.cookingTime
: cookingTime // ignore: cast_nullable_to_non_nullable
as int,
ingredients: null == ingredients
ingredients: freezed == ingredients
? _value.ingredients
: ingredients // ignore: cast_nullable_to_non_nullable
as List<Ingredient>,
as List<Ingredient>?,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
steps: null == steps
steps: freezed == steps
? _value.steps
: steps // ignore: cast_nullable_to_non_nullable
as List<Step>,
videoUrl: null == videoUrl
as List<Step>?,
videoUrl: freezed == videoUrl
? _value.videoUrl
: videoUrl // ignore: cast_nullable_to_non_nullable
as String,
as String?,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
@@ -163,14 +163,14 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
title: null == title
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
type: null == type
as String?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
as String?,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
@@ -225,14 +225,14 @@ abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> {
String image,
String state,
@JsonKey(name: 'cooking_time') int cookingTime,
List<Ingredient> ingredients,
List<Ingredient>? ingredients,
String description,
List<Step> steps,
@JsonKey(name: 'video_url') String videoUrl,
List<Step>? steps,
@JsonKey(name: 'video_url') String? videoUrl,
@JsonKey(name: 'created_at') DateTime createdAt,
@JsonKey(name: 'updated_at') DateTime updatedAt,
String title,
String type,
String? title,
String? type,
String slug,
@JsonKey(name: 'total_comments') int commentsCount,
@JsonKey(name: 'was_liked_by_me') bool wasLikedByMe,
@@ -262,14 +262,14 @@ class __$$RecipesImplCopyWithImpl<$Res>
Object? image = null,
Object? state = null,
Object? cookingTime = null,
Object? ingredients = null,
Object? ingredients = freezed,
Object? description = null,
Object? steps = null,
Object? videoUrl = null,
Object? steps = freezed,
Object? videoUrl = freezed,
Object? createdAt = null,
Object? updatedAt = null,
Object? title = null,
Object? type = null,
Object? title = freezed,
Object? type = freezed,
Object? slug = null,
Object? commentsCount = null,
Object? wasLikedByMe = null,
@@ -295,22 +295,22 @@ class __$$RecipesImplCopyWithImpl<$Res>
? _value.cookingTime
: cookingTime // ignore: cast_nullable_to_non_nullable
as int,
ingredients: null == ingredients
ingredients: freezed == ingredients
? _value._ingredients
: ingredients // ignore: cast_nullable_to_non_nullable
as List<Ingredient>,
as List<Ingredient>?,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
steps: null == steps
steps: freezed == steps
? _value._steps
: steps // ignore: cast_nullable_to_non_nullable
as List<Step>,
videoUrl: null == videoUrl
as List<Step>?,
videoUrl: freezed == videoUrl
? _value.videoUrl
: videoUrl // ignore: cast_nullable_to_non_nullable
as String,
as String?,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
@@ -319,14 +319,14 @@ class __$$RecipesImplCopyWithImpl<$Res>
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
title: null == title
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
type: null == type
as String?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
as String?,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
@@ -367,9 +367,9 @@ class _$RecipesImpl implements _Recipes {
required this.image,
required this.state,
@JsonKey(name: 'cooking_time') required this.cookingTime,
required final List<Ingredient> ingredients,
required final List<Ingredient>? ingredients,
required this.description,
required final List<Step> steps,
required final List<Step>? steps,
@JsonKey(name: 'video_url') required this.videoUrl,
@JsonKey(name: 'created_at') required this.createdAt,
@JsonKey(name: 'updated_at') required this.updatedAt,
@@ -397,27 +397,31 @@ class _$RecipesImpl implements _Recipes {
@override
@JsonKey(name: 'cooking_time')
final int cookingTime;
final List<Ingredient> _ingredients;
final List<Ingredient>? _ingredients;
@override
List<Ingredient> get ingredients {
List<Ingredient>? get ingredients {
final value = _ingredients;
if (value == null) return null;
if (_ingredients is EqualUnmodifiableListView) return _ingredients;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_ingredients);
return EqualUnmodifiableListView(value);
}
@override
final String description;
final List<Step> _steps;
final List<Step>? _steps;
@override
List<Step> get steps {
List<Step>? get steps {
final value = _steps;
if (value == null) return null;
if (_steps is EqualUnmodifiableListView) return _steps;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_steps);
return EqualUnmodifiableListView(value);
}
@override
@JsonKey(name: 'video_url')
final String videoUrl;
final String? videoUrl;
@override
@JsonKey(name: 'created_at')
final DateTime createdAt;
@@ -425,9 +429,9 @@ class _$RecipesImpl implements _Recipes {
@JsonKey(name: 'updated_at')
final DateTime updatedAt;
@override
final String title;
final String? title;
@override
final String type;
final String? type;
@override
final String slug;
@override
@@ -537,14 +541,14 @@ abstract class _Recipes implements Recipes {
required final String image,
required final String state,
@JsonKey(name: 'cooking_time') required final int cookingTime,
required final List<Ingredient> ingredients,
required final List<Ingredient>? ingredients,
required final String description,
required final List<Step> steps,
@JsonKey(name: 'video_url') required final String videoUrl,
required final List<Step>? steps,
@JsonKey(name: 'video_url') required final String? videoUrl,
@JsonKey(name: 'created_at') required final DateTime createdAt,
@JsonKey(name: 'updated_at') required final DateTime updatedAt,
required final String title,
required final String type,
required final String? title,
required final String? type,
required final String slug,
@JsonKey(name: 'total_comments') required final int commentsCount,
@JsonKey(name: 'was_liked_by_me') required final bool wasLikedByMe,
@@ -565,14 +569,14 @@ abstract class _Recipes implements Recipes {
@JsonKey(name: 'cooking_time')
int get cookingTime;
@override
List<Ingredient> get ingredients;
List<Ingredient>? get ingredients;
@override
String get description;
@override
List<Step> get steps;
List<Step>? get steps;
@override
@JsonKey(name: 'video_url')
String get videoUrl;
String? get videoUrl;
@override
@JsonKey(name: 'created_at')
DateTime get createdAt;
@@ -580,9 +584,9 @@ abstract class _Recipes implements Recipes {
@JsonKey(name: 'updated_at')
DateTime get updatedAt;
@override
String get title;
String? get title;
@override
String get type;
String? get type;
@override
String get slug;
@override

View File

@@ -12,18 +12,18 @@ _$RecipesImpl _$$RecipesImplFromJson(Map<String, dynamic> json) =>
image: json['image'] as String,
state: json['state'] as String,
cookingTime: (json['cooking_time'] as num).toInt(),
ingredients: (json['ingredients'] as List<dynamic>)
.map((e) => Ingredient.fromJson(e as Map<String, dynamic>))
ingredients: (json['ingredients'] as List<dynamic>?)
?.map((e) => Ingredient.fromJson(e as Map<String, dynamic>))
.toList(),
description: json['description'] as String,
steps: (json['steps'] as List<dynamic>)
.map((e) => Step.fromJson(e as Map<String, dynamic>))
steps: (json['steps'] as List<dynamic>?)
?.map((e) => Step.fromJson(e as Map<String, dynamic>))
.toList(),
videoUrl: json['video_url'] as String,
videoUrl: json['video_url'] as String?,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
title: json['title'] as String,
type: json['type'] as String,
title: json['title'] as String?,
type: json['type'] as String?,
slug: json['slug'] as String,
commentsCount: (json['total_comments'] as num).toInt(),
wasLikedByMe: json['was_liked_by_me'] as bool,

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -18,7 +20,8 @@ class ConsultantsCubit extends Cubit<ConsultantsState> {
isLoading: false,
),
);
} catch (e) {
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(ConsultantsState.error(errorMessage: e.toString()));
}
}

View File

@@ -107,9 +107,10 @@ class DietsCubit extends Cubit<DietsState> {
errorMessage: null,
),
);
} catch (e) {
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(
state.copyWith(errorMessage: e.toString()),
state.copyWith(isLoading: false),
);
}
}
@@ -143,14 +144,13 @@ class DietsCubit extends Cubit<DietsState> {
);
}
} catch (e) {
emit(state.copyWith(errorMessage: e.toString()));
emit(state.copyWith(isLoading: false));
}
}
}
Future<void> likeDiet(int id, int index) async {
final currentDiets = state.dietsList;
// if (currentDiets.isEmpty) return;
final currentDiets = state.dietsList;
final dietsForModification = [...currentDiets];
final currentDiet = currentDiets[index];
if (currentDiet.wasLikedByMe) {

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer';
class RecipesCubit extends Cubit<RecipesState> {
final FeedRepository feedRepository;
@@ -38,7 +39,8 @@ class RecipesCubit extends Cubit<RecipesState> {
),
);
lastPage = currentPage;
} catch (e) {
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(RecipesState.error(errorMessage: e.toString()));
}
}

View File

@@ -1,5 +1,6 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/widgets/add_item_button.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
@@ -14,6 +15,7 @@ import 'package:eatvio_app/features/feed/presentation/widgets/chip_group.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/consultants.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diets.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/plans.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
@@ -25,7 +27,6 @@ import 'package:go_router/go_router.dart';
import '../widgets/recipes.dart';
class FeedScreen extends StatelessWidget {
const FeedScreen({super.key});
@@ -260,10 +261,23 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name),
),
2 => AddItemButton(
onTap: state.profile!.roles.contains('consultant') ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null,
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
final result = await GoRouter.of(context).pushNamed<String?>(CreateDietScreen.name);
if (result != null) {
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
child: SuccessPublishedDietBottomSheet(
title: result,
),
);
}
}
: null,
),
3 => AddItemButton(
onTap: state.profile!.roles.contains('consultant') ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => AddItemButton()
},

View File

@@ -63,4 +63,4 @@ class Diets extends StatelessWidget {
),
);
}
}
}

View File

@@ -75,7 +75,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
children: [
_iconButton('assets/icons/clock.svg', widget.recipes.cookingTime.toString() + ' минут'),
SizedBox(width: 8),
_iconButton('assets/icons/steps.svg', widget.recipes.steps.length.toString() + ' этапов'),
_iconButton('assets/icons/steps.svg', '${widget.recipes.steps?.length.toString()}' + ' этапов'),
SizedBox(width: 8),
_iconButton('assets/icons/calories.svg', widget.recipes.calories.toStringAsFixed(0) + ' ккал'),
],
@@ -87,7 +87,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
widget.recipes.title,
widget.recipes.title ?? '',
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black87,

View File

@@ -0,0 +1,54 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class SuccessPublishedDietBottomSheet extends StatelessWidget {
const SuccessPublishedDietBottomSheet({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
title,
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: () {
GoRouter.of(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,
),
],
);
}
}

View File

@@ -0,0 +1,172 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
class InternetConnectionCheckerScreen extends StatefulWidget {
const InternetConnectionCheckerScreen({super.key, required this.child});
final Widget child;
@override
State<InternetConnectionCheckerScreen> createState() => _InternetConnectionCheckerScreenState();
}
class _InternetConnectionCheckerScreenState extends State<InternetConnectionCheckerScreen> {
bool _hasInternet = true;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
late final AppLifecycleListener _listener;
@override
void initState() {
super.initState();
_checkConnection();
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateAccessStatus);
_listener = AppLifecycleListener(
onResume: () async {
_connectivitySubscription.resume();
await _checkConnection();
},
onHide: _connectivitySubscription.pause,
onPause: _connectivitySubscription.pause,
);
}
Future<void> _checkConnection() async {
late List<ConnectivityResult> result;
try {
result = await _connectivity.checkConnectivity();
} on PlatformException catch (e) {
log('Couldn\'t check connectivity status', error: e);
return;
}
return _updateAccessStatus(result);
}
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
final hasInternet = await _checkInternetAccess(result);
setState(() {
_hasInternet = hasInternet;
});
}
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
if (result.contains(ConnectivityResult.none)) {
return false;
}
try {
final response = await Future.any([
http.get(Uri.parse('https://google.com')),
http.get(Uri.parse('https://yandex.ru')),
http.get(Uri.parse('https://dev2.eatvio.com')),
http.get(Uri.parse('https://eatvio.ru')),
]).timeout(Duration(seconds: 5));
return response.statusCode >= 100 && response.statusCode < 600;
} on SocketException catch (_) {
return false;
} on TimeoutException catch (_) {
return false;
} on HandshakeException catch (_) {
return false;
} on CertificateException catch (_) {
return false;
} on http.ClientException catch (_) {
return false;
} catch (e, st) {
log(e.toString(), stackTrace: st);
return false;
}
}
@override
void dispose() {
_connectivitySubscription.cancel();
_listener.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (!_hasInternet)
NoInternetScreen(
onTryAgain: () async {
EasyDebounce.debounce(
'internet connection check',
Duration(seconds: 2),
() async {
await _checkConnection();
},
);
},
),
],
);
}
}
class NoInternetScreen extends StatelessWidget {
const NoInternetScreen({super.key, required this.onTryAgain});
final Future<void> Function() onTryAgain;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Spacer(),
SizedBox(height: 28),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 52),
child: Text(
'Отсутствует подключение к интернету',
textAlign: TextAlign.center,
style: TextStyles.headingXLSemibold.copyWith(
color: appTheme.black,
),
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Text(
'Проверьте соединение с сетью и попробуйте ещё раз',
textAlign: TextAlign.center,
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray,
),
),
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CustomOutlinedButton(
onPressed: onTryAgain,
height: 52,
text: 'Попробовать ещё раз',
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
side: WidgetStatePropertyAll(BorderSide.none),
),
),
),
Spacer(),
],
),
);
}
}

View File

@@ -1,5 +1,8 @@
import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -28,6 +31,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
'name',
'name:desc',
];
static const List<int?> _durationValues = [null, ...durationValues];
static const List<String?> _types = [null, ...dietTypeValues];
@@ -39,7 +43,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _activeQuery,
),
);
@@ -48,7 +52,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _draftQuery,
),
);
@@ -57,7 +61,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _personalQuery,
),
);
@@ -70,7 +74,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _activeQuery,
),
);
@@ -79,7 +83,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _draftQuery,
),
);
@@ -88,7 +92,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _personalQuery,
),
);
@@ -101,7 +105,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _activeQuery,
),
);
@@ -110,7 +114,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _draftQuery,
),
);
@@ -119,7 +123,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
EasyDebounce.debounce(
'loadDiets',
Duration(seconds: 1),
() => loadDiets(
() => loadCurrentDiets(
query: _personalQuery,
),
);
@@ -128,9 +132,34 @@ class MyDietsCubit extends Cubit<MyDietsState> {
void changeSelectedStateIndex(int index) {
emit(state.copyWith(selectedStateIndex: index));
if (state.activeDiets.isEmpty) {
EasyDebounce.debounce(
'loadActiveDiets',
Duration(seconds: 1),
() => loadCurrentDiets(
query: _activeQuery,
),
);
} else if (state.draftDiets.isEmpty) {
EasyDebounce.debounce(
'loadDraftDiets',
Duration(seconds: 1),
() => loadCurrentDiets(
query: _draftQuery,
),
);
} else if (state.personalDiets.isEmpty) {
EasyDebounce.debounce(
'loadPersonalDiets',
Duration(seconds: 1),
() => loadCurrentDiets(
query: _personalQuery,
),
);
}
}
Future<void> loadDiets({
Future<void> loadCurrentDiets({
String? query,
}) async {
switch (state.selectedStateIndex) {
@@ -160,11 +189,13 @@ class MyDietsCubit extends Cubit<MyDietsState> {
final type = _types[state.activeFilterState.typeIndex];
final duration = _durationValues[state.activeFilterState.durationIndex];
final sorting = _sortingValues[state.activeFilterState.sortingIndex];
final dietState = DietState.active;
final dietsPagination = await myDietsRepository.getDiets(
query: query,
types: type,
duration: duration,
sorting: sorting,
state: dietState.toString(),
);
lastActivePage = dietsPagination.meta.lastPage;
emit(
@@ -174,7 +205,8 @@ class MyDietsCubit extends Cubit<MyDietsState> {
error: null,
),
);
} catch (e) {
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(
state.copyWith(error: e.toString()),
);
@@ -187,11 +219,13 @@ class MyDietsCubit extends Cubit<MyDietsState> {
final type = _types[state.draftFilterState.typeIndex];
final duration = _durationValues[state.draftFilterState.durationIndex];
final sorting = _sortingValues[state.draftFilterState.sortingIndex];
final dietState = DietState.draft;
final dietsPagination = await myDietsRepository.getDiets(
query: query,
types: type,
duration: duration,
sorting: sorting,
state: dietState.toString(),
);
lastActivePage = dietsPagination.meta.lastPage;
emit(
@@ -214,11 +248,13 @@ class MyDietsCubit extends Cubit<MyDietsState> {
final type = _types[state.personalFilterState.typeIndex];
final duration = _durationValues[state.personalFilterState.durationIndex];
final sorting = _sortingValues[state.personalFilterState.sortingIndex];
final dietState = DietState.personal;
final dietsPagination = await myDietsRepository.getDiets(
query: query,
types: type,
duration: duration,
sorting: sorting,
state: dietState.toString(),
);
lastActivePage = dietsPagination.meta.lastPage;
emit(

View File

@@ -33,7 +33,7 @@ class MyDietsScreenContent extends StatelessWidget {
final activeDietsScrollController = ScrollController();
final draftDietsScrollController = ScrollController();
final personalDietsScrollController = ScrollController();
final activeDietsSearchController = SearchController();
final draftDietsSearchController = SearchController();
final personalDietsSearchController = SearchController();
@@ -111,7 +111,7 @@ class MyDietsScreenContent extends StatelessWidget {
'searchDiets',
Duration(milliseconds: 500),
() {
context.read<MyDietsCubit>().loadDiets(query: query == '' ? null : query);
context.read<MyDietsCubit>().loadCurrentDiets(query: query == '' ? null : query);
},
);
},
@@ -133,7 +133,7 @@ class MyDietsScreenContent extends StatelessWidget {
];
},
body: RefreshIndicator(
onRefresh: () => context.read<MyDietsCubit>().loadDiets(),
onRefresh: () => context.read<MyDietsCubit>().loadCurrentDiets(),
child: BlocBuilder<MyDietsCubit, MyDietsState>(
builder: (BuildContext context, MyDietsState state) {
if (state.isLoading) {

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
@@ -110,6 +111,13 @@ class _MyDietWidgetState extends State<MyDietWidget> {
children: [
Expanded(
child: CustomOutlinedButton(
onPressed: () {
if (state.selectedStateIndex == 1) {
} else if (state.selectedStateIndex == 2) {
} else {
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet);
}
},
text: switch (state.selectedStateIndex) {
1 => s.publish,
2 => s.toOffer,
@@ -160,6 +168,12 @@ class _MyDietWidgetState extends State<MyDietWidget> {
children: [
Expanded(
child: CustomOutlinedButton(
onPressed: () {
if (state.selectedStateIndex == 0) {
} else {
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet);
}
},
text: switch (state.selectedStateIndex) {
0 => s.removeFromActive,
_ => s.edit,
@@ -178,51 +192,49 @@ class _MyDietWidgetState extends State<MyDietWidget> {
onPressed: () async {
final result = await context.read<MyDietsCubit>().deleteDiet(widget.diet.id);
if (result)
Future.delayed(Duration(seconds: 3), () {
showBottomSheetWithBlur(
context: context,
child: Column(
children: [
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
'${widget.diet.name}',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
Text(
s.deleted,
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),
),
showBottomSheetWithBlur(
context: context,
child: Column(
children: [
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
'${widget.diet.name}',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
Text(
s.deleted,
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,
),
],
),
);
});
),
SizedBox(
height: 20,
),
],
),
);
},
height: 52,
width: 52,

View File

@@ -20,6 +20,7 @@ class MyDietsRepository {
String? types,
int? duration,
String? sorting,
String? state,
}) async {
try {
return (await myDietsService.getDiets(
@@ -28,6 +29,7 @@ class MyDietsRepository {
types: types,
duration: duration,
sorting: sorting,
state: state,
));
} catch (e) {
rethrow;

View File

@@ -12,13 +12,14 @@ abstract class MyDietsService {
@GET('/api/profile/my_diets')
Future<DietsPagination> getDiets({
@Query('page') required int page,
@Query('filters[name][\$startsWith]') String? query,
@Query('filters[name][\$startsWith]') String? query,
@Query('filters[state][\$eq]') String? state,
@Query('filters[type][\$contains]') String? types,
@Query('filters[duration][\$eq]') int? duration,
@Query('sort') String? sorting,
});
@POST('/api/diets/delete/{dietId}')
@DELETE('/api/diets/delete/{dietId}')
Future<void> deleteDiet({
@Path() required int dietId,
});

View File

@@ -21,6 +21,7 @@ class _MyDietsService implements MyDietsService {
Future<DietsPagination> getDiets({
required int page,
String? query,
String? state,
String? types,
int? duration,
String? sorting,
@@ -29,6 +30,7 @@ class _MyDietsService implements MyDietsService {
final queryParameters = <String, dynamic>{
r'page': page,
r'filters[name][$startsWith]': query,
r'filters[state][$eq]': state,
r'filters[type][$contains]': types,
r'filters[duration][$eq]': duration,
r'sort': sorting,
@@ -64,7 +66,7 @@ class _MyDietsService implements MyDietsService {
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
Options(method: 'DELETE', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/diets/delete/${dietId}',

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,7 +16,7 @@ class PlansCubit extends Cubit<PlansState> {
try {
emit(PlansState.loaded(plansList: await plansRepository.getPlansBySlug(slug)));
} catch (e) {
print(e);
log(e.toString());
emit(PlansState.error(errorMessage: e.toString()));
}
}

View File

@@ -242,7 +242,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
_activePlans(ProfileStateLoaded state) {
if (state.profile.roles.contains('consultant')) {
if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
}
return BlocBuilder<PlansCubit, PlansState>(
@@ -372,7 +372,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
_diplomasAndCertificates(ProfileStateLoaded state) {
if (state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) {
if (!state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
@@ -456,7 +456,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
_education(ProfileStateLoaded state) {
if (state.profile.roles.contains('consultant') ||
if (!state.profile.roles.contains('consultant') ||
state.profile.educationResource?.completedCourses == null ||
state.profile.educationResource?.completedCourses?.length == 0) {
return const SizedBox.shrink();
@@ -549,7 +549,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
_socialLinks(ProfileStateLoaded state) {
if (state.profile.roles.contains('consultant')) {
if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
}
return Padding(

View File

@@ -84,8 +84,10 @@ class RecipeCreateEntity with _$RecipeCreateEntity {
TypeOfRecipe? typeOfRecipe,
int? countOfPortions,
double? energyInOnePortion,
String? totalCookTime, // hh:mm
String? activeCookTime, // hh:mm
int? totalCookHours,
int? totalCookMinutes,
int? activeCookHours,
int? activeCookMinutes,
String? description,
File? image,
String? videoUrl,
@@ -99,21 +101,28 @@ class RecipeCreateEntity with _$RecipeCreateEntity {
type: typeOfRecipe?.toApiType(),
servings: countOfPortions,
energyPerServing: energyInOnePortion?.toInt(),
totalCookingTime: totalCookTime?.split(':').fold(0, (value, element) => (int.tryParse(element) ?? 0)),
activeCookTime: activeCookTime?.split(':').fold(0, (value, element) => (int.tryParse(element) ?? 0)),
totalCookingTime: ((totalCookHours ?? 0) * 60) + (totalCookMinutes ?? 0),
activeCookTime: ((activeCookHours ?? 0) * 60) + (activeCookMinutes ?? 0),
description: description,
image: image?.path,
video_url: videoUrl,
ingredients: ingredients,
steps: steps.map((step) => Step(image: step.image?.path ?? '', description: step.description ?? '')).toList(),
steps: steps.map((step) {
if (step.hasProps()) return Step(image: step.image?.path ?? '', description: step.description ?? '');
}).toList(),
tags: tags,
);
}
@freezed
class StepEntity with _$StepEntity {
const StepEntity._();
const factory StepEntity({
String? description,
File? image,
}) = _StepEntity;
bool hasProps() {
return description != null;
}
}

View File

@@ -20,8 +20,10 @@ mixin _$RecipeCreateEntity {
TypeOfRecipe? get typeOfRecipe => throw _privateConstructorUsedError;
int? get countOfPortions => throw _privateConstructorUsedError;
double? get energyInOnePortion => throw _privateConstructorUsedError;
String? get totalCookTime => throw _privateConstructorUsedError; // hh:mm
String? get activeCookTime => throw _privateConstructorUsedError; // hh:mm
int? get totalCookHours => throw _privateConstructorUsedError;
int? get totalCookMinutes => throw _privateConstructorUsedError;
int? get activeCookHours => throw _privateConstructorUsedError;
int? get activeCookMinutes => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError;
File? get image => throw _privateConstructorUsedError;
String? get videoUrl => throw _privateConstructorUsedError;
@@ -47,8 +49,10 @@ abstract class $RecipeCreateEntityCopyWith<$Res> {
TypeOfRecipe? typeOfRecipe,
int? countOfPortions,
double? energyInOnePortion,
String? totalCookTime,
String? activeCookTime,
int? totalCookHours,
int? totalCookMinutes,
int? activeCookHours,
int? activeCookMinutes,
String? description,
File? image,
String? videoUrl,
@@ -76,8 +80,10 @@ class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity>
Object? typeOfRecipe = freezed,
Object? countOfPortions = freezed,
Object? energyInOnePortion = freezed,
Object? totalCookTime = freezed,
Object? activeCookTime = freezed,
Object? totalCookHours = freezed,
Object? totalCookMinutes = freezed,
Object? activeCookHours = freezed,
Object? activeCookMinutes = freezed,
Object? description = freezed,
Object? image = freezed,
Object? videoUrl = freezed,
@@ -102,14 +108,22 @@ class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity>
? _value.energyInOnePortion
: energyInOnePortion // ignore: cast_nullable_to_non_nullable
as double?,
totalCookTime: freezed == totalCookTime
? _value.totalCookTime
: totalCookTime // ignore: cast_nullable_to_non_nullable
as String?,
activeCookTime: freezed == activeCookTime
? _value.activeCookTime
: activeCookTime // ignore: cast_nullable_to_non_nullable
as String?,
totalCookHours: freezed == totalCookHours
? _value.totalCookHours
: totalCookHours // ignore: cast_nullable_to_non_nullable
as int?,
totalCookMinutes: freezed == totalCookMinutes
? _value.totalCookMinutes
: totalCookMinutes // ignore: cast_nullable_to_non_nullable
as int?,
activeCookHours: freezed == activeCookHours
? _value.activeCookHours
: activeCookHours // ignore: cast_nullable_to_non_nullable
as int?,
activeCookMinutes: freezed == activeCookMinutes
? _value.activeCookMinutes
: activeCookMinutes // ignore: cast_nullable_to_non_nullable
as int?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
@@ -151,8 +165,10 @@ abstract class _$$RecipeCreateEntityImplCopyWith<$Res>
TypeOfRecipe? typeOfRecipe,
int? countOfPortions,
double? energyInOnePortion,
String? totalCookTime,
String? activeCookTime,
int? totalCookHours,
int? totalCookMinutes,
int? activeCookHours,
int? activeCookMinutes,
String? description,
File? image,
String? videoUrl,
@@ -178,8 +194,10 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res>
Object? typeOfRecipe = freezed,
Object? countOfPortions = freezed,
Object? energyInOnePortion = freezed,
Object? totalCookTime = freezed,
Object? activeCookTime = freezed,
Object? totalCookHours = freezed,
Object? totalCookMinutes = freezed,
Object? activeCookHours = freezed,
Object? activeCookMinutes = freezed,
Object? description = freezed,
Object? image = freezed,
Object? videoUrl = freezed,
@@ -204,14 +222,22 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res>
? _value.energyInOnePortion
: energyInOnePortion // ignore: cast_nullable_to_non_nullable
as double?,
totalCookTime: freezed == totalCookTime
? _value.totalCookTime
: totalCookTime // ignore: cast_nullable_to_non_nullable
as String?,
activeCookTime: freezed == activeCookTime
? _value.activeCookTime
: activeCookTime // ignore: cast_nullable_to_non_nullable
as String?,
totalCookHours: freezed == totalCookHours
? _value.totalCookHours
: totalCookHours // ignore: cast_nullable_to_non_nullable
as int?,
totalCookMinutes: freezed == totalCookMinutes
? _value.totalCookMinutes
: totalCookMinutes // ignore: cast_nullable_to_non_nullable
as int?,
activeCookHours: freezed == activeCookHours
? _value.activeCookHours
: activeCookHours // ignore: cast_nullable_to_non_nullable
as int?,
activeCookMinutes: freezed == activeCookMinutes
? _value.activeCookMinutes
: activeCookMinutes // ignore: cast_nullable_to_non_nullable
as int?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
@@ -248,8 +274,10 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity {
this.typeOfRecipe,
this.countOfPortions,
this.energyInOnePortion,
this.totalCookTime,
this.activeCookTime,
this.totalCookHours,
this.totalCookMinutes,
this.activeCookHours,
this.activeCookMinutes,
this.description,
this.image,
this.videoUrl,
@@ -270,11 +298,13 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity {
@override
final double? energyInOnePortion;
@override
final String? totalCookTime;
// hh:mm
final int? totalCookHours;
@override
final String? activeCookTime;
// hh:mm
final int? totalCookMinutes;
@override
final int? activeCookHours;
@override
final int? activeCookMinutes;
@override
final String? description;
@override
@@ -307,7 +337,7 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity {
@override
String toString() {
return 'RecipeCreateEntity(title: $title, typeOfRecipe: $typeOfRecipe, countOfPortions: $countOfPortions, energyInOnePortion: $energyInOnePortion, totalCookTime: $totalCookTime, activeCookTime: $activeCookTime, description: $description, image: $image, videoUrl: $videoUrl, ingredients: $ingredients, steps: $steps, tags: $tags)';
return 'RecipeCreateEntity(title: $title, typeOfRecipe: $typeOfRecipe, countOfPortions: $countOfPortions, energyInOnePortion: $energyInOnePortion, totalCookHours: $totalCookHours, totalCookMinutes: $totalCookMinutes, activeCookHours: $activeCookHours, activeCookMinutes: $activeCookMinutes, description: $description, image: $image, videoUrl: $videoUrl, ingredients: $ingredients, steps: $steps, tags: $tags)';
}
@override
@@ -322,10 +352,14 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity {
other.countOfPortions == countOfPortions) &&
(identical(other.energyInOnePortion, energyInOnePortion) ||
other.energyInOnePortion == energyInOnePortion) &&
(identical(other.totalCookTime, totalCookTime) ||
other.totalCookTime == totalCookTime) &&
(identical(other.activeCookTime, activeCookTime) ||
other.activeCookTime == activeCookTime) &&
(identical(other.totalCookHours, totalCookHours) ||
other.totalCookHours == totalCookHours) &&
(identical(other.totalCookMinutes, totalCookMinutes) ||
other.totalCookMinutes == totalCookMinutes) &&
(identical(other.activeCookHours, activeCookHours) ||
other.activeCookHours == activeCookHours) &&
(identical(other.activeCookMinutes, activeCookMinutes) ||
other.activeCookMinutes == activeCookMinutes) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.image, image) || other.image == image) &&
@@ -344,8 +378,10 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity {
typeOfRecipe,
countOfPortions,
energyInOnePortion,
totalCookTime,
activeCookTime,
totalCookHours,
totalCookMinutes,
activeCookHours,
activeCookMinutes,
description,
image,
videoUrl,
@@ -369,8 +405,10 @@ abstract class _RecipeCreateEntity extends RecipeCreateEntity {
final TypeOfRecipe? typeOfRecipe,
final int? countOfPortions,
final double? energyInOnePortion,
final String? totalCookTime,
final String? activeCookTime,
final int? totalCookHours,
final int? totalCookMinutes,
final int? activeCookHours,
final int? activeCookMinutes,
final String? description,
final File? image,
final String? videoUrl,
@@ -388,9 +426,13 @@ abstract class _RecipeCreateEntity extends RecipeCreateEntity {
@override
double? get energyInOnePortion;
@override
String? get totalCookTime; // hh:mm
int? get totalCookHours;
@override
String? get activeCookTime; // hh:mm
int? get totalCookMinutes;
@override
int? get activeCookHours;
@override
int? get activeCookMinutes;
@override
String? get description;
@override
@@ -506,8 +548,8 @@ class __$$StepEntityImplCopyWithImpl<$Res>
/// @nodoc
class _$StepEntityImpl implements _StepEntity {
const _$StepEntityImpl({this.description, this.image});
class _$StepEntityImpl extends _StepEntity {
const _$StepEntityImpl({this.description, this.image}) : super._();
@override
final String? description;
@@ -541,9 +583,10 @@ class _$StepEntityImpl implements _StepEntity {
__$$StepEntityImplCopyWithImpl<_$StepEntityImpl>(this, _$identity);
}
abstract class _StepEntity implements StepEntity {
abstract class _StepEntity extends StepEntity {
const factory _StepEntity({final String? description, final File? image}) =
_$StepEntityImpl;
const _StepEntity._() : super._();
@override
String? get description;

View File

@@ -12,5 +12,5 @@ class Ingredient with _$Ingredient {
required String portion,
}) = _Ingredient;
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
}

View File

@@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:io';
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
@@ -11,10 +12,10 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
CreateRecipeCubit({required this.recipeRepository})
: super(
CreateRecipeState(
RecipeCreateEntity(
entity: RecipeCreateEntity(
typeOfRecipe: TypeOfRecipe.first_meals,
ingredients: [],
steps: [],
steps: [StepEntity()],
tags: [],
),
),
@@ -22,13 +23,11 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
addStep() {
emit(
CreateRecipeState(
state.entity.copyWith(
steps: [
...state.entity.steps,
StepEntity(),
],
),
state.copyWith.entity(
steps: [
...state.entity.steps,
StepEntity(),
],
),
);
}
@@ -36,21 +35,16 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
removeStep(StepEntity step) {
List<StepEntity> steps = [...state.entity.steps];
steps.remove(step);
emit(
CreateRecipeState(state.entity.copyWith(steps: steps)),
);
emit(state.copyWith.entity(steps: steps));
}
addIngredient(Ingredient entity) {
emit(
CreateRecipeState(
state.entity.copyWith(
ingredients: [
...state.entity.ingredients,
entity,
],
),
state.copyWith.entity(
ingredients: [
...state.entity.ingredients,
entity,
],
),
);
}
@@ -59,67 +53,35 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
List<Ingredient> ingredients = [...state.entity.ingredients];
ingredients.removeAt(index);
emit(
CreateRecipeState(
state.entity.copyWith(
ingredients: ingredients,
),
),
);
emit(state.copyWith.entity(ingredients: ingredients));
}
replaceIngredientPosition(int oldIndex, int newIndex) {
List<Ingredient> ingredients = [...state.entity.ingredients];
final item = ingredients.removeAt(oldIndex);
ingredients.insert(newIndex, item);
emit(
CreateRecipeState(
state.entity.copyWith(
ingredients: ingredients,
),
),
);
emit(state.copyWith.entity(ingredients: ingredients));
}
replaceTitle(String title) {
emit(
CreateRecipeState(
state.entity.copyWith(
title: title,
),
),
);
emit(state.copyWith.entity(title: title == '' ? null : title));
}
replaceDescription(String description) {
emit(
CreateRecipeState(
state.entity.copyWith(
description: description,
),
),
);
emit(state.copyWith.entity(description: description == '' ? null : description));
}
changeType(TypeOfRecipe typeOfRecipe) {
emit(
CreateRecipeState(
state.entity.copyWith(
typeOfRecipe: typeOfRecipe,
),
),
);
emit(state.copyWith.entity(typeOfRecipe: typeOfRecipe));
}
addTag(String tag) {
emit(
CreateRecipeState(
state.entity.copyWith(
tags: [
...state.entity.tags,
tag,
],
),
state.copyWith.entity(
tags: [
...state.entity.tags,
tag,
],
),
);
}
@@ -128,104 +90,153 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
List<String> tags = [...state.entity.tags];
tags.remove(tag);
emit(
CreateRecipeState(
state.entity.copyWith(
tags: tags,
),
),
);
emit(state.copyWith.entity(tags: tags));
}
replacePortionsCount(int count) {
emit(
CreateRecipeState(
state.entity.copyWith(
countOfPortions: count,
),
),
);
emit(state.copyWith.entity(countOfPortions: count));
}
replaceEnegryInPortion(double energy) {
emit(
CreateRecipeState(
state.entity.copyWith(
energyInOnePortion: energy,
),
),
);
emit(state.copyWith.entity(energyInOnePortion: energy));
}
replaceTotalTime(String time) {
emit(
CreateRecipeState(
state.entity.copyWith(
totalCookTime: time,
),
),
);
setTotalCookingMinutes(int minutes) {
emit(state.copyWith.entity(totalCookMinutes: minutes));
}
replaceActiveTime(String time) {
emit(
CreateRecipeState(
state.entity.copyWith(
activeCookTime: time,
),
),
);
setTotalCookingHours(int hours) {
emit(state.copyWith.entity(totalCookHours: hours));
}
setActiveCookingMinutes(int minutes) {
emit(state.copyWith.entity(activeCookMinutes: minutes));
}
setActiveCookingHours(int hours) {
emit(state.copyWith.entity(activeCookHours: hours));
}
replaceVideoUrl(String videoUrl) {
emit(
CreateRecipeState(
state.entity.copyWith(
videoUrl: videoUrl,
),
),
);
emit(state.copyWith.entity(videoUrl: videoUrl));
}
replaceImage(File? image) {
emit(
CreateRecipeState(
state.entity.copyWith(
image: image,
),
),
);
emit(state.copyWith.entity(image: image));
}
replaceDescriptionInStep(String description, int index) {
List<StepEntity> steps = [...state.entity.steps];
steps[index] = steps[index].copyWith(description: description);
emit(
CreateRecipeState(
state.entity.copyWith(
steps: steps,
),
),
);
emit(state.copyWith.entity(steps: steps));
}
replaceImageInStep(File? image, int index) {
List<StepEntity> steps = [...state.entity.steps];
steps[index] = steps[index].copyWith(image: image);
emit(
CreateRecipeState(
state.entity.copyWith(
steps: steps,
),
),
);
emit(state.copyWith.entity(steps: steps));
}
Future<void> publishRecipe() async {
Future<bool> publishRecipe() async {
try {
await recipeRepository.publishRecipe(state.entity.toModel());
} catch (e) {
rethrow;
final model = state.entity.toModel();
await recipeRepository.publishRecipe(model);
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
return false;
}
}
bool validateAll() {
final isTitleValid = validateTitle();
final isDescValid = validateDescription();
final isImageValid = validateImage();
final areServingsValid = validateServings();
final isCookTimeValid = validateTotalCookTime();
final isActiveCookTimeValid = validateActiveCookTime();
final isEnergyPerServingsValid = validateEnergyPerServing();
return isTitleValid && isDescValid && isImageValid && areServingsValid && isCookTimeValid && isActiveCookTimeValid && isEnergyPerServingsValid;
}
bool validateTitle() {
if (state.entity.title == null || (state.entity.title?.isEmpty ?? true)) {
emit(state.copyWith(titleValidationError: 'Поле не заполнено'));
return false;
} else if (state.entity.title!.length < 3) {
emit(state.copyWith(titleValidationError: 'Минимальная длина 3 символа '));
return false;
} else {
emit(state.copyWith(titleValidationError: null));
return true;
}
}
bool validateServings() {
if (state.entity.countOfPortions == null) {
emit(state.copyWith(servingsValidationError: 'Поле не заполнено'));
return false;
} else {
emit(state.copyWith(servingsValidationError: null));
return true;
}
}
bool validateEnergyPerServing() {
if (state.entity.energyInOnePortion == null) {
emit(state.copyWith(energyPerServingValidationError: 'Поле не заполнено'));
return false;
} else {
emit(state.copyWith(energyPerServingValidationError: null));
return true;
}
}
bool validateActiveCookTime() {
if (state.entity.activeCookHours == null && state.entity.activeCookMinutes == null) {
emit(state.copyWith(activeTimeValidationError: 'Поле не заполнено'));
return false;
} else {
emit(state.copyWith(activeTimeValidationError: null));
return true;
}
}
bool validateTotalCookTime() {
if (state.entity.totalCookHours == null || state.entity.totalCookMinutes == null) {
emit(state.copyWith(cookingTimeValidationError: 'Поле не заполнено'));
return false;
} else {
emit(state.copyWith(cookingTimeValidationError: null));
return true;
}
}
bool validateDescription() {
if (state.entity.description == null || (state.entity.description?.isEmpty ?? true)) {
emit(state.copyWith(descriptionValidationError: 'Поле не заполнено'));
return false;
} else if (state.entity.description!.length < 3) {
emit(state.copyWith(descriptionValidationError: 'Минимальная длина 3 символа '));
return false;
} else {
emit(state.copyWith(descriptionValidationError: null));
return true;
}
}
bool validateImage() {
if (state.entity.image == null) {
emit(state.copyWith(imageValidationError: 'Поле не заполнено'));
return false;
} else {
emit(state.copyWith(imageValidationError: null));
return true;
}
}
}

View File

@@ -4,8 +4,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'create_recipe_state.freezed.dart';
@freezed
class CreateRecipeState with _$CreateRecipeState {
const factory CreateRecipeState(
RecipeCreateEntity entity,
) = _CreateRecipeState;
class CreateRecipeState with _$CreateRecipeState {
const factory CreateRecipeState({
required RecipeCreateEntity entity,
String? titleValidationError,
String? descriptionValidationError,
String? cookingTimeValidationError,
String? activeTimeValidationError,
String? servingsValidationError,
String? energyPerServingValidationError,
String? imageValidationError,
}) = _CreateRecipeState;
}

View File

@@ -17,6 +17,14 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$CreateRecipeState {
RecipeCreateEntity get entity => throw _privateConstructorUsedError;
String? get titleValidationError => throw _privateConstructorUsedError;
String? get descriptionValidationError => throw _privateConstructorUsedError;
String? get cookingTimeValidationError => throw _privateConstructorUsedError;
String? get activeTimeValidationError => throw _privateConstructorUsedError;
String? get servingsValidationError => throw _privateConstructorUsedError;
String? get energyPerServingValidationError =>
throw _privateConstructorUsedError;
String? get imageValidationError => throw _privateConstructorUsedError;
/// Create a copy of CreateRecipeState
/// with the given fields replaced by the non-null parameter values.
@@ -31,7 +39,15 @@ abstract class $CreateRecipeStateCopyWith<$Res> {
CreateRecipeState value, $Res Function(CreateRecipeState) then) =
_$CreateRecipeStateCopyWithImpl<$Res, CreateRecipeState>;
@useResult
$Res call({RecipeCreateEntity entity});
$Res call(
{RecipeCreateEntity entity,
String? titleValidationError,
String? descriptionValidationError,
String? cookingTimeValidationError,
String? activeTimeValidationError,
String? servingsValidationError,
String? energyPerServingValidationError,
String? imageValidationError});
$RecipeCreateEntityCopyWith<$Res> get entity;
}
@@ -52,12 +68,48 @@ class _$CreateRecipeStateCopyWithImpl<$Res, $Val extends CreateRecipeState>
@override
$Res call({
Object? entity = null,
Object? titleValidationError = freezed,
Object? descriptionValidationError = freezed,
Object? cookingTimeValidationError = freezed,
Object? activeTimeValidationError = freezed,
Object? servingsValidationError = freezed,
Object? energyPerServingValidationError = freezed,
Object? imageValidationError = freezed,
}) {
return _then(_value.copyWith(
entity: null == entity
? _value.entity
: entity // ignore: cast_nullable_to_non_nullable
as RecipeCreateEntity,
titleValidationError: freezed == titleValidationError
? _value.titleValidationError
: titleValidationError // ignore: cast_nullable_to_non_nullable
as String?,
descriptionValidationError: freezed == descriptionValidationError
? _value.descriptionValidationError
: descriptionValidationError // ignore: cast_nullable_to_non_nullable
as String?,
cookingTimeValidationError: freezed == cookingTimeValidationError
? _value.cookingTimeValidationError
: cookingTimeValidationError // ignore: cast_nullable_to_non_nullable
as String?,
activeTimeValidationError: freezed == activeTimeValidationError
? _value.activeTimeValidationError
: activeTimeValidationError // ignore: cast_nullable_to_non_nullable
as String?,
servingsValidationError: freezed == servingsValidationError
? _value.servingsValidationError
: servingsValidationError // ignore: cast_nullable_to_non_nullable
as String?,
energyPerServingValidationError: freezed ==
energyPerServingValidationError
? _value.energyPerServingValidationError
: energyPerServingValidationError // ignore: cast_nullable_to_non_nullable
as String?,
imageValidationError: freezed == imageValidationError
? _value.imageValidationError
: imageValidationError // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
@@ -80,7 +132,15 @@ abstract class _$$CreateRecipeStateImplCopyWith<$Res>
__$$CreateRecipeStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({RecipeCreateEntity entity});
$Res call(
{RecipeCreateEntity entity,
String? titleValidationError,
String? descriptionValidationError,
String? cookingTimeValidationError,
String? activeTimeValidationError,
String? servingsValidationError,
String? energyPerServingValidationError,
String? imageValidationError});
@override
$RecipeCreateEntityCopyWith<$Res> get entity;
@@ -100,12 +160,48 @@ class __$$CreateRecipeStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? entity = null,
Object? titleValidationError = freezed,
Object? descriptionValidationError = freezed,
Object? cookingTimeValidationError = freezed,
Object? activeTimeValidationError = freezed,
Object? servingsValidationError = freezed,
Object? energyPerServingValidationError = freezed,
Object? imageValidationError = freezed,
}) {
return _then(_$CreateRecipeStateImpl(
null == entity
entity: null == entity
? _value.entity
: entity // ignore: cast_nullable_to_non_nullable
as RecipeCreateEntity,
titleValidationError: freezed == titleValidationError
? _value.titleValidationError
: titleValidationError // ignore: cast_nullable_to_non_nullable
as String?,
descriptionValidationError: freezed == descriptionValidationError
? _value.descriptionValidationError
: descriptionValidationError // ignore: cast_nullable_to_non_nullable
as String?,
cookingTimeValidationError: freezed == cookingTimeValidationError
? _value.cookingTimeValidationError
: cookingTimeValidationError // ignore: cast_nullable_to_non_nullable
as String?,
activeTimeValidationError: freezed == activeTimeValidationError
? _value.activeTimeValidationError
: activeTimeValidationError // ignore: cast_nullable_to_non_nullable
as String?,
servingsValidationError: freezed == servingsValidationError
? _value.servingsValidationError
: servingsValidationError // ignore: cast_nullable_to_non_nullable
as String?,
energyPerServingValidationError: freezed ==
energyPerServingValidationError
? _value.energyPerServingValidationError
: energyPerServingValidationError // ignore: cast_nullable_to_non_nullable
as String?,
imageValidationError: freezed == imageValidationError
? _value.imageValidationError
: imageValidationError // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@@ -113,14 +209,36 @@ class __$$CreateRecipeStateImplCopyWithImpl<$Res>
/// @nodoc
class _$CreateRecipeStateImpl implements _CreateRecipeState {
const _$CreateRecipeStateImpl(this.entity);
const _$CreateRecipeStateImpl(
{required this.entity,
this.titleValidationError,
this.descriptionValidationError,
this.cookingTimeValidationError,
this.activeTimeValidationError,
this.servingsValidationError,
this.energyPerServingValidationError,
this.imageValidationError});
@override
final RecipeCreateEntity entity;
@override
final String? titleValidationError;
@override
final String? descriptionValidationError;
@override
final String? cookingTimeValidationError;
@override
final String? activeTimeValidationError;
@override
final String? servingsValidationError;
@override
final String? energyPerServingValidationError;
@override
final String? imageValidationError;
@override
String toString() {
return 'CreateRecipeState(entity: $entity)';
return 'CreateRecipeState(entity: $entity, titleValidationError: $titleValidationError, descriptionValidationError: $descriptionValidationError, cookingTimeValidationError: $cookingTimeValidationError, activeTimeValidationError: $activeTimeValidationError, servingsValidationError: $servingsValidationError, energyPerServingValidationError: $energyPerServingValidationError, imageValidationError: $imageValidationError)';
}
@override
@@ -128,11 +246,42 @@ class _$CreateRecipeStateImpl implements _CreateRecipeState {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CreateRecipeStateImpl &&
(identical(other.entity, entity) || other.entity == entity));
(identical(other.entity, entity) || other.entity == entity) &&
(identical(other.titleValidationError, titleValidationError) ||
other.titleValidationError == titleValidationError) &&
(identical(other.descriptionValidationError,
descriptionValidationError) ||
other.descriptionValidationError ==
descriptionValidationError) &&
(identical(other.cookingTimeValidationError,
cookingTimeValidationError) ||
other.cookingTimeValidationError ==
cookingTimeValidationError) &&
(identical(other.activeTimeValidationError,
activeTimeValidationError) ||
other.activeTimeValidationError == activeTimeValidationError) &&
(identical(
other.servingsValidationError, servingsValidationError) ||
other.servingsValidationError == servingsValidationError) &&
(identical(other.energyPerServingValidationError,
energyPerServingValidationError) ||
other.energyPerServingValidationError ==
energyPerServingValidationError) &&
(identical(other.imageValidationError, imageValidationError) ||
other.imageValidationError == imageValidationError));
}
@override
int get hashCode => Object.hash(runtimeType, entity);
int get hashCode => Object.hash(
runtimeType,
entity,
titleValidationError,
descriptionValidationError,
cookingTimeValidationError,
activeTimeValidationError,
servingsValidationError,
energyPerServingValidationError,
imageValidationError);
/// Create a copy of CreateRecipeState
/// with the given fields replaced by the non-null parameter values.
@@ -145,11 +294,32 @@ class _$CreateRecipeStateImpl implements _CreateRecipeState {
}
abstract class _CreateRecipeState implements CreateRecipeState {
const factory _CreateRecipeState(final RecipeCreateEntity entity) =
_$CreateRecipeStateImpl;
const factory _CreateRecipeState(
{required final RecipeCreateEntity entity,
final String? titleValidationError,
final String? descriptionValidationError,
final String? cookingTimeValidationError,
final String? activeTimeValidationError,
final String? servingsValidationError,
final String? energyPerServingValidationError,
final String? imageValidationError}) = _$CreateRecipeStateImpl;
@override
RecipeCreateEntity get entity;
@override
String? get titleValidationError;
@override
String? get descriptionValidationError;
@override
String? get cookingTimeValidationError;
@override
String? get activeTimeValidationError;
@override
String? get servingsValidationError;
@override
String? get energyPerServingValidationError;
@override
String? get imageValidationError;
/// Create a copy of CreateRecipeState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -212,7 +212,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.recipes.title,
widget.recipes.title ?? '',
style: TextStyle(
color: Colors.black,
fontSize: 24,
@@ -241,7 +241,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
Icon(Icons.fast_forward_outlined),
SizedBox(width: 4),
Text(
widget.recipes.steps.length.toString() + ' этапов',
'${widget.recipes.steps?.length ?? 0}' + ' этапов',
style: TextStyle(
color: Colors.black,
fontSize: 12,
@@ -286,9 +286,14 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _ingredient(widget.recipes.ingredients[index]),
itemBuilder: (context, index) {
if (widget.recipes.ingredients != null) {
return _ingredient(widget.recipes.ingredients![index]);
}
return SizedBox.shrink();
},
separatorBuilder: (context, index) => SizedBox(height: 4),
itemCount: widget.recipes.ingredients.length,
itemCount: widget.recipes.ingredients?.length ?? 0,
),
SizedBox(height: 12),
Padding(
@@ -307,7 +312,10 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _step(widget.recipes.steps[index], index + 1),
itemBuilder: (context, index) {
if (widget.recipes.steps == null) return SizedBox.shrink();
return _step(widget.recipes.steps![index], index + 1);
},
separatorBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(16),
child: Container(
@@ -318,7 +326,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
),
),
),
itemCount: widget.recipes.steps.length,
itemCount: widget.recipes.steps?.length ?? 0,
),
SizedBox(height: 8),
Padding(

View File

@@ -1,6 +1,8 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/info_about_recipe_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/ingredients_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/steps_widget.dart';
@@ -42,32 +44,44 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
InfoAboutRecipeWidget(),
IngredientsWidget(),
StepsWidget(),
Builder(
builder: (context) {
return InkWell(
onTap: () {
context.read<CreateRecipeCubit>().publishRecipe();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: appTheme.blueA400,
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
builder: (context, state) {
return Builder(
builder: (context) {
return InkWell(
onTap: () async {
if (context.read<CreateRecipeCubit>().validateAll()) {
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe();
if (isSuccess) {
showSuccessToast('Рецепт успешно опубликован');
Navigator.pop(context);
} else {
showErrorToast('Не удалось опубликовать рецепт');
}
}
},
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Опубликовать',
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w600,
child: Ink(
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Опубликовать',
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
),
),
);
},
);
},
),

View File

@@ -8,14 +8,13 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
class ImageWidget extends StatefulWidget {
const ImageWidget({super.key});
const ImageWidget({super.key, required this.image});
final File? image;
@override
State<ImageWidget> createState() => _ImageWidgetState();
}
class _ImageWidgetState extends State<ImageWidget> {
XFile? image;
late final CreateRecipeCubit createRecipeCubit;
@override
@@ -30,7 +29,7 @@ class _ImageWidgetState extends State<ImageWidget> {
onTap: () {
_showImagePicker();
},
child: image == null
child: widget.image == null
? SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/icons/upload_photo_of_portion.svg',
@@ -40,7 +39,7 @@ class _ImageWidgetState extends State<ImageWidget> {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.file(
File(image!.path),
File(widget.image!.path),
),
),
Positioned(
@@ -48,9 +47,6 @@ class _ImageWidgetState extends State<ImageWidget> {
top: 16,
child: GestureDetector(
onTap: () {
setState(() {
image = null;
});
createRecipeCubit.replaceImage(null);
},
child: Container(
@@ -76,10 +72,11 @@ class _ImageWidgetState extends State<ImageWidget> {
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.camera);
final image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image!.path));
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
Navigator.of(context).pop();
},
@@ -94,10 +91,11 @@ class _ImageWidgetState extends State<ImageWidget> {
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
final image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image!.path));
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
Navigator.of(context).pop();
},

View File

@@ -1,8 +1,11 @@
import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/image_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/portion_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/tags_widget.dart';
@@ -40,10 +43,29 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
children: [
Text('Название'),
SizedBox(height: 4),
CustomTextFormField(
hintText: 'Например: тар-тар из говядины',
onChanged: (value) {
createRecipeCubit.replaceTitle(value);
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
buildWhen: (previous, current) => previous.titleValidationError != current.titleValidationError,
builder: (context, state) {
return Column(
children: [
CustomTextFormField(
hintText: 'Например: тар-тар из говядины',
hasError: state.titleValidationError != null,
onChanged: (value) {
createRecipeCubit.replaceTitle(value);
if (state.titleValidationError != null) {
createRecipeCubit.validateTitle();
}
},
),
if (state.titleValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.titleValidationError!,
),
],
],
);
},
),
const SizedBox(height: 12),
@@ -70,18 +92,57 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
const SizedBox(height: 12),
Text('Описание'),
SizedBox(height: 4),
CustomTextFormField(
hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.',
minLines: 5,
maxLines: 5,
onChanged: (value) {
createRecipeCubit.replaceDescription(value);
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
buildWhen: (previous, current) => previous.descriptionValidationError != current.descriptionValidationError,
builder: (context, state) {
return Column(
children: [
CustomTextFormField(
hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.',
minLines: 5,
maxLines: 5,
hasError: state.descriptionValidationError != null,
onChanged: (value) {
createRecipeCubit.replaceDescription(value);
if (state.descriptionValidationError != null) {
createRecipeCubit.validateDescription();
}
},
),
if (state.descriptionValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.descriptionValidationError!,
),
],
],
);
},
),
const SizedBox(height: 12),
Text('Фото обложка'),
SizedBox(height: 4),
ImageWidget(),
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
buildWhen: (previous, current) => previous.imageValidationError != current.imageValidationError || previous.entity.image != current.entity.image,
builder: (context, state) {
return Column(
children: [
Container(
child: ImageWidget(image: state.entity.image),
decoration: state.imageValidationError != null
? BoxDecoration(border: Border.all(width: 1, color: appTheme.red), borderRadius: BorderRadius.circular(8))
: null,
),
if (state.imageValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.imageValidationError!,
),
],
],
);
},
),
const SizedBox(height: 12),
Text('Ссылка на видео (если есть)'),
SizedBox(height: 4),

View File

@@ -69,7 +69,7 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
color: Colors.grey.shade600,
),
title: Text(state.entity.ingredients[index].name),
subtitle: Text('${state.entity.ingredients[index].unit} ${state.entity.ingredients[index].portion == 'gr' ? 'гр' : 'шт'}'),
subtitle: Text('${state.entity.ingredients[index].portion} ${state.entity.ingredients[index].unit == 'gr' ? 'гр' : 'шт'}'),
trailing: IconButton(
onPressed: () {
createRecipeCubit.removeIngredient(index);
@@ -163,8 +163,8 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
createRecipeCubit.addIngredient(
Ingredient(
name: nameController.text,
unit: gramsController.text.isEmpty ? pcsController.text : gramsController.text,
portion: gramsController.text.isEmpty ? 'pcs' : 'gr',
unit: gramsController.text.isEmpty ? 'pcs' : 'gr',
portion: gramsController.text.isEmpty ? pcsController.text : gramsController.text,
),
);
Navigator.of(context).pop();

View File

@@ -1,5 +1,7 @@
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -12,7 +14,6 @@ class PortionWidget extends StatefulWidget {
class _PortionWidgetState extends State<PortionWidget> {
late final CreateRecipeCubit createRecipeCubit;
@override
void initState() {
super.initState();
@@ -21,49 +22,86 @@ class _PortionWidgetState extends State<PortionWidget> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
return BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
buildWhen: (previous, current) =>
previous.servingsValidationError != current.servingsValidationError ||
previous.energyPerServingValidationError != current.energyPerServingValidationError,
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Кол-во порций'),
SizedBox(height: 4),
CustomTextFormField(
hintText: '1',
onChanged: (value) {
createRecipeCubit.replacePortionsCount(int.parse(value));
},
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Кол-во порций'),
SizedBox(height: 4),
Column(
children: [
CustomTextFormField(
hintText: '1',
hasError: state.servingsValidationError != null,
textInputType: TextInputType.number,
onChanged: (value) {
createRecipeCubit.replacePortionsCount(int.parse(value));
if (state.servingsValidationError != null) {
createRecipeCubit.validateServings();
}
},
),
if (state.servingsValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.servingsValidationError!,
),
],
],
),
],
),
],
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ккал в одной порции'),
SizedBox(height: 4),
CustomTextFormField(
hintText: '100',
suffixText: 'Ккал',
onChanged: (value) {
createRecipeCubit.replaceEnegryInPortion(double.parse(value));
},
),
const SizedBox(width: 8),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ккал в одной порции'),
SizedBox(height: 4),
Column(
children: [
CustomTextFormField(
hintText: '100',
hasError: state.energyPerServingValidationError != null,
suffixText: 'Ккал',
textInputType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
createRecipeCubit.replaceEnegryInPortion(double.parse(value));
if (state.energyPerServingValidationError != null) {
createRecipeCubit.validateEnergyPerServing();
}
},
),
if (state.energyPerServingValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.energyPerServingValidationError!,
),
],
],
),
],
),
],
),
),
],
),
],
),
],
);
},
);
}
}

View File

@@ -1,6 +1,8 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -22,130 +24,190 @@ class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Text('Общее время приготовления'),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(
'Общее время приготовления - это период времени от начала приготовления блюда до подачи его на стол.',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
height: 1.2,
),
),
),
);
},
child: Icon(
Icons.help_outline_rounded,
color: appTheme.blueA400,
size: 20,
),
),
],
),
SizedBox(height: 4),
Row(
return BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
buildWhen: (previous, current) =>
previous.activeTimeValidationError != current.activeTimeValidationError || previous.cookingTimeValidationError != current.cookingTimeValidationError,
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: CustomTextFormField(
hintText: 'Часов',
onChanged: (value) {
if (createRecipeCubit.state.entity.totalCookTime == null) {
createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:00');
} else {
createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.totalCookTime!.substring(3, 5)}');
}
},
),
),
const SizedBox(width: 8),
Expanded(
child: CustomTextFormField(
hintText: 'Минут',
onChanged: (value) {
if (createRecipeCubit.state.entity.totalCookTime == null) {
createRecipeCubit.replaceTotalTime('00:${value.padLeft(2, '0')}');
} else {
createRecipeCubit.replaceTotalTime('${createRecipeCubit.state.entity.totalCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}');
}
},
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Text('Активное время приготовления'),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(
'Активное время приготовления - это период времени, требующий активного участия в приготовлении блюда. Оно включает мытье и очистку продуктов, нарезку, шинковку, взбивание, перемешивание, размешивание, перекладывание и т.п.',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
height: 1.2,
Row(
children: [
Text('Общее время приготовления'),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(
'Общее время приготовления - это период времени от начала приготовления блюда до подачи его на стол.',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
height: 1.2,
),
),
),
),
);
},
child: Icon(
Icons.help_outline_rounded,
color: appTheme.blueA400,
size: 20,
),
);
},
child: Icon(
Icons.help_outline_rounded,
color: appTheme.blueA400,
size: 20,
),
),
],
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
children: [
CustomTextFormField(
hintText: 'Часов',
hasError: state.cookingTimeValidationError != null,
textInputType: TextInputType.number,
onChanged: (value) {
final hours = int.tryParse(value);
if (hours == null) return;
createRecipeCubit.setTotalCookingHours(hours);
if (state.cookingTimeValidationError != null) {
createRecipeCubit.validateTotalCookTime();
}
},
),
if (state.cookingTimeValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.cookingTimeValidationError!,
),
],
],
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
children: [
CustomTextFormField(
hintText: 'Минут',
hasError: state.cookingTimeValidationError != null,
textInputType: TextInputType.number,
onChanged: (value) {
final minutes = int.tryParse(value);
if (minutes == null) return;
createRecipeCubit.setTotalCookingMinutes(minutes);
if (state.cookingTimeValidationError != null) {
createRecipeCubit.validateTotalCookTime();
}
},
),
if (state.cookingTimeValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.cookingTimeValidationError!,
),
],
],
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Text('Активное время приготовления'),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(
'Активное время приготовления - это период времени, требующий активного участия в приготовлении блюда. Оно включает мытье и очистку продуктов, нарезку, шинковку, взбивание, перемешивание, размешивание, перекладывание и т.п.',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
height: 1.2,
),
),
),
);
},
child: Icon(
Icons.help_outline_rounded,
color: appTheme.blueA400,
size: 20,
),
),
],
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
children: [
CustomTextFormField(
hintText: 'Часов',
hasError: state.activeTimeValidationError != null,
textInputType: TextInputType.number,
onChanged: (value) {
final hours = int.tryParse(value);
if (hours == null) return;
createRecipeCubit.setActiveCookingHours(hours);
if (state.activeTimeValidationError != null) {
createRecipeCubit.validateActiveCookTime();
}
},
),
if (state.activeTimeValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.activeTimeValidationError!,
),
],
],
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
children: [
CustomTextFormField(
hintText: 'Минут',
hasError: state.activeTimeValidationError != null,
textInputType: TextInputType.number,
onChanged: (value) {
final minutes = int.tryParse(value);
if (minutes == null) return;
createRecipeCubit.setActiveCookingMinutes(minutes);
if (state.activeTimeValidationError != null) {
createRecipeCubit.validateActiveCookTime();
}
},
),
if (state.activeTimeValidationError != null) ...[
SizedBox(height: 4),
CustomErrorWidget(
error: state.activeTimeValidationError!,
),
],
],
),
),
],
),
],
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: CustomTextFormField(
hintText: 'Часов',
onChanged: (value) {
if (createRecipeCubit.state.entity.activeCookTime == null) {
createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:00');
} else {
createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.activeCookTime!.substring(3, 5)}');
}
},
),
),
const SizedBox(width: 8),
Expanded(
child: CustomTextFormField(
hintText: 'Минут',
onChanged: (value) {
if (createRecipeCubit.state.entity.activeCookTime == null) {
createRecipeCubit.replaceActiveTime('00:${value.padLeft(2, '0')}');
} else {
createRecipeCubit.replaceActiveTime('${createRecipeCubit.state.entity.activeCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}');
}
},
),
),
],
),
],
);
},
);
}
}

View File

@@ -1,8 +1,11 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/recipes/models/step.dart';
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
import 'package:eatvio_app/features/recipes/models/product.dart';
@@ -19,58 +22,56 @@ class RecipeRepository {
}
}
Future<RecipeModel?> publishRecipe(RecipeModel recipe) async {
Future<void> publishRecipe(RecipeModel recipe) async {
try {
if (recipe.image == null || (recipe.image?.isEmpty ?? true)) {
return null;
return;
}
final file = File(recipe.image ?? '');
final image = await ImageCompressUtility.compressFile(File(recipe.image ?? ''));
final multiPartFile = await MultipartFile.fromFile(image.path);
final ingredientsMap = recipe.ingredients?.map((ingredient) {
return {
'name': ingredient!.name,
'unit': ingredient.unit,
'portion': ingredient.portion,
};
if (ingredient != null) {
return ingredient.toJson();
}
}).toList() ??
[];
final stepsMap = recipe.steps?.map((step) {
return {
'description': step?.description ?? "",
};
}).toList() ??
[];
// recipe.steps?.map((step) {
// return File(step?.image ?? "");
// }).toList() ??
// [];
if (recipe.type != null &&
recipe.description != null &&
recipe.totalCookingTime != null &&
recipe.activeCookTime != null &&
recipe.servings != null &&
recipe.energyPerServing != null &&
ingredientsMap.isNotEmpty) {
return await recipeService.publishRecipe(
type: recipe.type!,
state: 'active',
title: recipe.title,
tags: recipe.tags,
description: recipe.description!,
video_url: recipe.video_url,
ingredients: ingredientsMap,
steps: stepsMap,
cooking_time: recipe.totalCookingTime!,
active_time: recipe.activeCookTime!,
image: file,
servings: recipe.servings!,
energy_per_serving: recipe.energyPerServing!,
);
final stepsMap = [];
for (final Step? step in recipe.steps ?? []) {
if (step != null) {
if ((step.image == null || (step.image?.isEmpty ?? true)) && step.description != null) {
stepsMap.add({
'description': step.description ?? "",
});
} else if (step.image != null && (step.image?.isNotEmpty ?? false)) {
final image = await ImageCompressUtility.compressFile(File(step.image ?? ''));
final multipartFile = await MultipartFile.fromFile(image.path);
stepsMap.add({
'description': step.description ?? "",
'image': multipartFile,
});
}
}
}
return null;
final formMap = {
'type': recipe.type,
'state': 'active',
'title': recipe.title,
'tags': recipe.tags,
'description': recipe.description,
'video_url': recipe.video_url,
'ingredients': ingredientsMap,
'steps': stepsMap,
'cooking_time': recipe.totalCookingTime,
'active_time': recipe.activeCookTime,
'image': multiPartFile,
'servings': recipe.servings,
'energy_per_serving': recipe.energyPerServing,
};
formMap.removeWhere((k, v) => v == null || (v is String && v.isEmpty));
final formData = FormData.fromMap(formMap);
await recipeService.publishRecipe(formData: formData);
} catch (e) {
rethrow;
}

View File

@@ -1,6 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
@@ -19,24 +16,10 @@ abstract class RecipeService {
@GET('/api/diary/product_nutritional_values/filter')
Future<Map<String, List<Product>>> getInputProduct(@Query('name') String name);
@POST('/api/recipes/create')
@MultiPart()
Future<RecipeModel> publishRecipe({
@Part() required String type,
@Part() required String state,
@Part() String? title,
@Part() required String description,
@Part() String? video_url,
@Part() List<Map<String, Object>>? ingredients,
@Part() List<Map<String, Object>>? steps,
@Part() List<String>? tags,
@Part() int? cooking_time,
@Part() int? active_time,
@Part() required int servings,
@Part() required int energy_per_serving,
@Part() File? image,
Future<void> publishRecipe({
@Body() required FormData formData,
});
@POST('/api/recipes/update/{id}')
@@ -44,4 +27,4 @@ abstract class RecipeService {
@GET('/api/recipes/tags')
Future<RecipeTagModel> getTags();
}
}

View File

@@ -79,68 +79,12 @@ class _RecipeService implements RecipeService {
}
@override
Future<RecipeModel> publishRecipe({
required String type,
required String state,
String? title,
required String description,
String? video_url,
List<Map<String, Object>>? ingredients,
List<Map<String, Object>>? steps,
List<String>? tags,
int? cooking_time,
int? active_time,
required int servings,
required int energy_per_serving,
File? image,
}) async {
Future<void> publishRecipe({required FormData formData}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
_data.fields.add(MapEntry('type', type));
_data.fields.add(MapEntry('state', state));
if (title != null) {
_data.fields.add(MapEntry('title', title));
}
_data.fields.add(MapEntry('description', description));
if (video_url != null) {
_data.fields.add(MapEntry('video_url', video_url));
}
ingredients?.forEach((i) {
_data.fields.add(MapEntry('ingredients', jsonEncode(i)));
});
steps?.forEach((i) {
_data.fields.add(MapEntry('steps', jsonEncode(i)));
});
tags?.forEach((i) {
_data.fields.add(MapEntry('tags', i));
});
if (cooking_time != null) {
_data.fields.add(MapEntry('cooking_time', cooking_time.toString()));
}
if (active_time != null) {
_data.fields.add(MapEntry('active_time', active_time.toString()));
}
_data.fields.add(MapEntry('servings', servings.toString()));
_data.fields.add(
MapEntry('energy_per_serving', energy_per_serving.toString()),
);
if (image != null) {
if (image != null) {
_data.files.add(
MapEntry(
'image',
MultipartFile.fromFileSync(
image.path,
filename: image.path.split(Platform.pathSeparator).last,
),
),
);
}
}
final _options = _setStreamType<RecipeModel>(
final _data = formData;
final _options = _setStreamType<void>(
Options(
method: 'POST',
headers: _headers,
@@ -155,15 +99,7 @@ class _RecipeService implements RecipeService {
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late RecipeModel _value;
try {
_value = RecipeModel.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
await _dio.fetch<void>(_options);
}
@override

View File

@@ -56,7 +56,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
const SizedBox(height: 12),
_aboutEatvio(state),
const SizedBox(height: 12),
if (state.profile.roles.contains('consultant'))
if (!state.profile.roles.contains('consultant'))
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _becomeConsultantButton(state),

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/internet_connection_checker/internet_connection_checker_screen.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -44,6 +45,7 @@ class MyApp extends StatelessWidget {
child: MaterialApp.router(
theme: ThemeHelper().themeData(),
routerConfig: AppRouter.appRouter,
builder: (context, child) => InternetConnectionCheckerScreen(child: child!),
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
S.delegate,

View File

@@ -60,6 +60,7 @@ dependencies:
flutter_native_splash: ^2.4.4
webview_flutter: ^4.10.0
sentry_flutter: ^8.13.0
connectivity_plus: ^6.1.2
dev_dependencies:
build_runner: ^2.4.14