diet creation scrren is done

This commit is contained in:
Username1548
2024-12-11 19:15:25 +07:00
parent 2e0a8c559f
commit 2f168a2f35
63 changed files with 3113 additions and 633 deletions

View File

@@ -1,7 +1,10 @@
import 'dart:ui';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
@@ -11,6 +14,7 @@ import 'package:eatvio_app/features/create_diet/presentation/widgets/day_widget.
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_picker_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_type_choice_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -38,63 +42,96 @@ class CreateDietScreenContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
centerTitle: true,
title: Text(
s.create_diet,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 8),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
SizedBox(height: 16),
CustomTextFormField(
hintText: s.enter_diet_name,
onChanged: (value) {
context.read<CreateDietCubit>().setName(value);
},
),
SizedBox(height: 12),
SizedBox(
height: 132,
child: CustomTextFormField(
textAlignVertical: TextAlignVertical.top,
maxLines: null,
minLines: null,
expands: true,
hintText: s.enter_diet_description,
onChanged: (value) {
context.read<CreateDietCubit>().setDetails(value);
},
),
),
SizedBox(height: 12),
DietDurationDropDownWidget(),
SizedBox(height: 12),
StatusDropDownWidget(),
SizedBox(height: 16),
DietTypeChoiceWidget(),
SizedBox(height: 16),
DietImagePickerWidget(),
SizedBox(height: 16),
],
),
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
if (state.published) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.pop();
showPublishSuccessBottomSheet(
context: context,
text: '${state.dietData.name ?? 'Диета'}',
);
});
} else if (state.drafted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.pop();
showDraftSuccessBottomSheet(
context: context,
text: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
);
});
}
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
centerTitle: true,
title: Text(
s.create_diet,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
SizedBox(height: 16),
BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return MainBackgroundBouble(
),
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 8),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16),
CustomTextFormField(
hintText: s.enter_diet_name,
hasError: state.dietNameError != null,
onChanged: (value) {
context.read<CreateDietCubit>().setName(value);
},
),
if (state.dietNameError != null) ...[
SizedBox(
height: 4,
),
CustomErrorWidget(error: state.dietNameError ?? ''),
],
SizedBox(height: 12),
SizedBox(
height: 132,
child: CustomTextFormField(
hasError: state.dietDescriptionError != null,
textAlignVertical: TextAlignVertical.top,
maxLines: null,
minLines: null,
expands: true,
hintText: s.enter_diet_description,
onChanged: (value) {
context.read<CreateDietCubit>().setDetails(value);
},
),
),
if (state.dietDescriptionError != null) ...[
SizedBox(
height: 4,
),
CustomErrorWidget(error: state.dietDescriptionError ?? ''),
],
SizedBox(height: 12),
DietDurationDropDownWidget(),
SizedBox(height: 12),
StatusDropDownWidget(),
SizedBox(height: 16),
DietTypeChoiceWidget(),
SizedBox(height: 16),
DietImagePickerWidget(),
SizedBox(height: 16),
],
),
),
SizedBox(height: 16),
MainBackgroundBouble(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Center(
@@ -106,71 +143,223 @@ class CreateDietScreenContent extends StatelessWidget {
),
),
),
);
},
),
SizedBox(height: 8),
BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return ListView.separated(
),
SizedBox(height: 8),
ListView.separated(
padding: const EdgeInsets.all(0),
itemBuilder: (context, index) => DayWidget(index: index),
itemCount: state.dietData.dailySchedule?.length ?? 0,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 8),
);
},
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
CustomOutlinedButton(
onPressed: () async {
await context.read<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);
},
text: s.preview,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
),
),
SizedBox(height: 12),
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().publishDiet();
},
text: s.publish,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
],
),
),
SizedBox(height: 20),
],
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
),
);
},
);
}
Future<void> showDraftSuccessBottomSheet({
required BuildContext context,
required String text,
bool hasBlur = true,
bool enableDrag = true,
}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: appTheme.transparentBlack,
backgroundColor: Colors.transparent,
elevation: 0,
useSafeArea: true,
enableDrag: enableDrag,
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: hasBlur ? 24 : 0,
sigmaY: hasBlur ? 24 : 0,
tileMode: TileMode.mirror,
),
child: SizedBox(
child: Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: appTheme.whiteA700,
),
child: Column(
children: [
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().setDietState(2);
},
text: s.to_draft,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
text,
textAlign: TextAlign.center,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
context.pop();
},
text: 'Хорошо',
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
SizedBox(height: 12),
CustomOutlinedButton(
onPressed: () async {
final diet = await context.read<CreateDietCubit>().getDietForPreview();
GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
},
text: s.preview,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
),
),
SizedBox(height: 12),
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().publishDiet();
},
text: s.publish,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
SizedBox(
height: 20,
),
],
),
),
SizedBox(height: 20),
],
),
),
),
);
}
Future<void> showPublishSuccessBottomSheet({
required BuildContext context,
required String text,
bool hasBlur = true,
bool enableDrag = true,
}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: appTheme.transparentBlack,
backgroundColor: Colors.transparent,
elevation: 0,
useSafeArea: true,
enableDrag: enableDrag,
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: hasBlur ? 24 : 0,
sigmaY: hasBlur ? 24 : 0,
tileMode: TileMode.mirror,
),
child: SizedBox(
child: Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: appTheme.whiteA700,
),
child: Column(
children: [
SizedBox(
height: 20,
),
BottomSheetHandlerBar(),
SizedBox(
height: 20,
),
Text(
text,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
Text(
'опубликованна',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
context.pop();
},
text: 'Хорошо',
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
SizedBox(
height: 20,
),
],
),
),
),
),
),
);

View File

@@ -1,5 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'dart:developer';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
@@ -18,11 +21,11 @@ class CreateDietCubit extends Cubit<CreateDietState> {
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
void setDetails(String text) {
emit(state.copyWith(dietData: state.dietData.copyWith(details: text)));
emit(state.copyWith(dietData: state.dietData.copyWith(details: text), dietDescriptionError: null));
}
void setName(String name) {
emit(state.copyWith(dietData: state.dietData.copyWith(name: name)));
emit(state.copyWith(dietData: state.dietData.copyWith(name: name), dietNameError: null));
}
void setDuration(int index) {
@@ -70,6 +73,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
dietData: state.dietData.copyWith(
duration: durationValues[index],
),
dietDurationError: null,
),
);
}
@@ -79,21 +83,25 @@ class CreateDietCubit extends Cubit<CreateDietState> {
}
void setImage(XFile? image) {
emit(state.copyWith(dietData: state.dietData.copyWith(image: image)));
emit(state.copyWith(dietData: state.dietData.copyWith(image: image), dietImageError: null));
}
void setDailySchedule(List<List<List<DailySchedule>>> dailySchedule) {
emit(state.copyWith(dietData: state.dietData.copyWith(dailySchedule: dailySchedule)));
emit(state.copyWith(dietData: state.dietData.copyWith(dailySchedule: dailySchedule), dietDurationError: null));
}
void setDietState(int index) {
emit(state.copyWith(dietStateIndex: index, dietData: state.dietData.copyWith(state: _dietStateValues[index])));
emit(state.copyWith(dietStateIndex: index, dietData: state.dietData.copyWith(state: _dietStateValues[index]), dietStateError: null));
}
void setDietType({required String key, required bool value}) {
final type = Map<String, bool>.from(state.dietData.type);
type[key] = value;
emit(state.copyWith(dietData: state.dietData.copyWith(type: type)));
emit(
state.copyWith(
dietData: state.dietData.copyWith(type: type),
),
);
}
void addPortion({required int dayIndex, required int mealIndex, required DailySchedule portion}) {
@@ -108,6 +116,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule);
emit(state.copyWith(dietData: newDietData));
_isDayValid(dayIndex);
}
}
@@ -170,10 +179,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
deepCopyOfDailySchedule[dayIndex] = copiedDay;
final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule);
emit(state.copyWith(dietData: newDietData));
_isDayValid(dayIndex);
}
}
Future<Diet> getDietForPreview() async {
Future<Diet?> getDietForPreview() async {
if (!_isFromValid()) return null;
final authorProfile = await profileRepository.getMyProfile();
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
final imageInBase64 = base64.encode(imageBytes);
@@ -232,10 +243,10 @@ class CreateDietCubit extends Cubit<CreateDietState> {
duration: state.dietData.duration ?? 0,
state: state.dietData.state ?? '',
type: DietType(
meat: state.dietData.type[0],
veg: state.dietData.type[1],
carb: state.dietData.type[2],
light: state.dietData.type[3],
meat: state.dietData.type['meat'],
veg: state.dietData.type['veg'],
carb: state.dietData.type['carb'],
light: state.dietData.type['light'],
),
details: state.dietData.details ?? '',
dailySchedule: state.dietData.dailySchedule ?? [],
@@ -256,25 +267,110 @@ class CreateDietCubit extends Cubit<CreateDietState> {
return diet;
}
Future<void> publishDiet() async {
Future<String?> publishDiet({bool toDraft = false}) async {
if (!_isFromValid()) return null;
try {
// final types = jsonEncode(state.dietData.type);
// if (state.dietData.image == null) {
// return;
// }
// final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
// final diet = await createDietRepository.createDiet(
// name: state.dietData.name,
// duration: state.dietData.duration,
// state: state.dietData.state,
// image: image,
// type: types,
// details: state.dietData.details,
// );
final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
final dietState = toDraft ? _dietStateValues.last : state.dietData.state;
final diet = await createDietRepository.createDiet(
name: state.dietData.name,
duration: state.dietData.duration,
state: dietState,
image: image,
meatType: state.dietData.type['meat'] == true ? '1' : '0',
carbType: state.dietData.type['carb'] == true ? '1' : '0',
vegType: state.dietData.type['veg'] == true ? '1' : '0',
lightType: state.dietData.type['light'] == true ? '1' : '0',
details: state.dietData.details,
);
final dailySchedule = state.dietData.dailySchedule;
createDietRepository.createDietDailySchedule(
id: diet.id,
model: {'daily_schedule': dailySchedule},
);
emit(state.copyWith(published: !toDraft, drafted: toDraft));
return state.dietData.name;
} catch (e, stackTrace) {
print(e);
print('---------------------------------');
print(stackTrace);
log(e.toString(), stackTrace: stackTrace);
}
return null;
}
bool _isFromValid() {
bool result = true;
if ((state.dietData.name?.length ?? 0) < 3) {
result = false;
emit(state.copyWith(dietNameError: 'Длинна названия не может быть меньше 3 символов'));
}
if (state.dietData.name == null || state.dietData.name == '') {
result = false;
emit(state.copyWith(dietNameError: 'Поле не заполненно'));
}
if ((state.dietData.details?.length ?? 0) < 3) {
result = false;
emit(state.copyWith(dietDescriptionError: 'Длинна описания не может быть меньше 3 символов'));
}
if (state.dietData.details == null || state.dietData.details == '') {
result = false;
emit(state.copyWith(dietDescriptionError: 'Поле не заполненно'));
}
if (state.dietData.duration == null) {
result = false;
emit(state.copyWith(dietDurationError: 'Срок диеты не выбран'));
}
if (state.dietData.state == null) {
result = false;
emit(state.copyWith(dietStateError: 'Статус диеты не выбран'));
}
if (state.dietData.image == null) {
result = false;
emit(state.copyWith(dietImageError: 'Не добавлена обложка'));
}
result = _isDailyScheduleValid();
return result;
}
bool _isDailyScheduleValid() {
final dailySchedule = state.dietData.dailySchedule;
bool result = true;
if (dailySchedule == null) {
result = false;
} else {
final dailyScheduleErrors = state.dailyScheduleErrors.map((element) {
return element.map((element) => element).toList();
}).toList();
for (final (dayIndex, day) in dailySchedule.indexed) {
for (final (mealIndex, meal) in day.indexed) {
if (meal.isEmpty) {
result = false;
dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты';
}
}
}
emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
}
return result;
}
bool _isDayValid(int dayIndex) {
final dailySchedule = state.dietData.dailySchedule;
bool result = true;
if (dailySchedule == null) {
result = false;
} else {
final dailyScheduleErrors = state.dailyScheduleErrors.map((element) {
return element.map((element) => element).toList();
}).toList();
for (final (mealIndex, meal) in dailySchedule[dayIndex].indexed) {
if (meal.isEmpty) {
result = false;
dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты';
} else {
dailyScheduleErrors[dayIndex][mealIndex] = '';
}
}
emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors));
}
return result;
}
}

View File

@@ -12,11 +12,20 @@ class CreateDietState with _$CreateDietState {
required String? errorMessage,
required CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
String? dietNameError,
String? dietDescriptionError,
String? dietDurationError,
String? dietStateError,
String? dietImageError,
required List<List<String>> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex,
@Default(false) bool published,
@Default(false) bool drafted,
}) = _CreateDietState;
factory CreateDietState.initial() => CreateDietState(
dailyScheduleErrors: List.filled(21, ['', '', '', '', '']),
isLoading: false,
errorMessage: null,
dietData: CreateDietModel(

View File

@@ -21,8 +21,17 @@ mixin _$CreateDietState {
CreateDietModel get dietData => throw _privateConstructorUsedError;
List<List<DailySchedule>>? get copiedDay =>
throw _privateConstructorUsedError;
String? get dietNameError => throw _privateConstructorUsedError;
String? get dietDescriptionError => throw _privateConstructorUsedError;
String? get dietDurationError => throw _privateConstructorUsedError;
String? get dietStateError => throw _privateConstructorUsedError;
String? get dietImageError => throw _privateConstructorUsedError;
List<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;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -42,8 +51,16 @@ abstract class $CreateDietStateCopyWith<$Res> {
String? errorMessage,
CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
String? dietNameError,
String? dietDescriptionError,
String? dietDurationError,
String? dietStateError,
String? dietImageError,
List<List<String>> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex});
int? dietStateIndex,
bool published,
bool drafted});
$CreateDietModelCopyWith<$Res> get dietData;
}
@@ -67,8 +84,16 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
Object? dietNameError = freezed,
Object? dietDescriptionError = freezed,
Object? dietDurationError = freezed,
Object? dietStateError = freezed,
Object? dietImageError = freezed,
Object? dailyScheduleErrors = null,
Object? durationIndex = freezed,
Object? dietStateIndex = freezed,
Object? published = null,
Object? drafted = null,
}) {
return _then(_value.copyWith(
isLoading: null == isLoading
@@ -87,6 +112,30 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
? _value.copiedDay
: copiedDay // ignore: cast_nullable_to_non_nullable
as List<List<DailySchedule>>?,
dietNameError: freezed == dietNameError
? _value.dietNameError
: dietNameError // ignore: cast_nullable_to_non_nullable
as String?,
dietDescriptionError: freezed == dietDescriptionError
? _value.dietDescriptionError
: dietDescriptionError // ignore: cast_nullable_to_non_nullable
as String?,
dietDurationError: freezed == dietDurationError
? _value.dietDurationError
: dietDurationError // ignore: cast_nullable_to_non_nullable
as String?,
dietStateError: freezed == dietStateError
? _value.dietStateError
: dietStateError // ignore: cast_nullable_to_non_nullable
as String?,
dietImageError: freezed == dietImageError
? _value.dietImageError
: dietImageError // ignore: cast_nullable_to_non_nullable
as String?,
dailyScheduleErrors: null == dailyScheduleErrors
? _value.dailyScheduleErrors
: dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
as List<List<String>>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -95,6 +144,14 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState>
? _value.dietStateIndex
: dietStateIndex // ignore: cast_nullable_to_non_nullable
as int?,
published: null == published
? _value.published
: published // ignore: cast_nullable_to_non_nullable
as bool,
drafted: null == drafted
? _value.drafted
: drafted // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -122,8 +179,16 @@ abstract class _$$CreateDietStateImplCopyWith<$Res>
String? errorMessage,
CreateDietModel dietData,
List<List<DailySchedule>>? copiedDay,
String? dietNameError,
String? dietDescriptionError,
String? dietDurationError,
String? dietStateError,
String? dietImageError,
List<List<String>> dailyScheduleErrors,
int? durationIndex,
int? dietStateIndex});
int? dietStateIndex,
bool published,
bool drafted});
@override
$CreateDietModelCopyWith<$Res> get dietData;
@@ -146,8 +211,16 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
Object? errorMessage = freezed,
Object? dietData = null,
Object? copiedDay = freezed,
Object? dietNameError = freezed,
Object? dietDescriptionError = freezed,
Object? dietDurationError = freezed,
Object? dietStateError = freezed,
Object? dietImageError = freezed,
Object? dailyScheduleErrors = null,
Object? durationIndex = freezed,
Object? dietStateIndex = freezed,
Object? published = null,
Object? drafted = null,
}) {
return _then(_$CreateDietStateImpl(
isLoading: null == isLoading
@@ -166,6 +239,30 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
? _value._copiedDay
: copiedDay // ignore: cast_nullable_to_non_nullable
as List<List<DailySchedule>>?,
dietNameError: freezed == dietNameError
? _value.dietNameError
: dietNameError // ignore: cast_nullable_to_non_nullable
as String?,
dietDescriptionError: freezed == dietDescriptionError
? _value.dietDescriptionError
: dietDescriptionError // ignore: cast_nullable_to_non_nullable
as String?,
dietDurationError: freezed == dietDurationError
? _value.dietDurationError
: dietDurationError // ignore: cast_nullable_to_non_nullable
as String?,
dietStateError: freezed == dietStateError
? _value.dietStateError
: dietStateError // ignore: cast_nullable_to_non_nullable
as String?,
dietImageError: freezed == dietImageError
? _value.dietImageError
: dietImageError // ignore: cast_nullable_to_non_nullable
as String?,
dailyScheduleErrors: null == dailyScheduleErrors
? _value._dailyScheduleErrors
: dailyScheduleErrors // ignore: cast_nullable_to_non_nullable
as List<List<String>>,
durationIndex: freezed == durationIndex
? _value.durationIndex
: durationIndex // ignore: cast_nullable_to_non_nullable
@@ -174,6 +271,14 @@ class __$$CreateDietStateImplCopyWithImpl<$Res>
? _value.dietStateIndex
: dietStateIndex // ignore: cast_nullable_to_non_nullable
as int?,
published: null == published
? _value.published
: published // ignore: cast_nullable_to_non_nullable
as bool,
drafted: null == drafted
? _value.drafted
: drafted // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -186,9 +291,18 @@ class _$CreateDietStateImpl implements _CreateDietState {
required this.errorMessage,
required this.dietData,
final List<List<DailySchedule>>? copiedDay,
this.dietNameError,
this.dietDescriptionError,
this.dietDurationError,
this.dietStateError,
this.dietImageError,
required final List<List<String>> dailyScheduleErrors,
this.durationIndex,
this.dietStateIndex})
: _copiedDay = copiedDay;
this.dietStateIndex,
this.published = false,
this.drafted = false})
: _copiedDay = copiedDay,
_dailyScheduleErrors = dailyScheduleErrors;
@override
final bool isLoading;
@@ -206,14 +320,39 @@ class _$CreateDietStateImpl implements _CreateDietState {
return EqualUnmodifiableListView(value);
}
@override
final String? dietNameError;
@override
final String? dietDescriptionError;
@override
final String? dietDurationError;
@override
final String? dietStateError;
@override
final String? dietImageError;
final List<List<String>> _dailyScheduleErrors;
@override
List<List<String>> get dailyScheduleErrors {
if (_dailyScheduleErrors is EqualUnmodifiableListView)
return _dailyScheduleErrors;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_dailyScheduleErrors);
}
@override
final int? durationIndex;
@override
final int? dietStateIndex;
@override
@JsonKey()
final bool published;
@override
@JsonKey()
final bool drafted;
@override
String toString() {
return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex)';
return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, dietNameError: $dietNameError, dietDescriptionError: $dietDescriptionError, dietDurationError: $dietDurationError, dietStateError: $dietStateError, dietImageError: $dietImageError, dailyScheduleErrors: $dailyScheduleErrors, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex, published: $published, drafted: $drafted)';
}
@override
@@ -229,10 +368,25 @@ class _$CreateDietStateImpl implements _CreateDietState {
other.dietData == dietData) &&
const DeepCollectionEquality()
.equals(other._copiedDay, _copiedDay) &&
(identical(other.dietNameError, dietNameError) ||
other.dietNameError == dietNameError) &&
(identical(other.dietDescriptionError, dietDescriptionError) ||
other.dietDescriptionError == dietDescriptionError) &&
(identical(other.dietDurationError, dietDurationError) ||
other.dietDurationError == dietDurationError) &&
(identical(other.dietStateError, dietStateError) ||
other.dietStateError == dietStateError) &&
(identical(other.dietImageError, dietImageError) ||
other.dietImageError == dietImageError) &&
const DeepCollectionEquality()
.equals(other._dailyScheduleErrors, _dailyScheduleErrors) &&
(identical(other.durationIndex, durationIndex) ||
other.durationIndex == durationIndex) &&
(identical(other.dietStateIndex, dietStateIndex) ||
other.dietStateIndex == dietStateIndex));
other.dietStateIndex == dietStateIndex) &&
(identical(other.published, published) ||
other.published == published) &&
(identical(other.drafted, drafted) || other.drafted == drafted));
}
@override
@@ -242,8 +396,16 @@ class _$CreateDietStateImpl implements _CreateDietState {
errorMessage,
dietData,
const DeepCollectionEquality().hash(_copiedDay),
dietNameError,
dietDescriptionError,
dietDurationError,
dietStateError,
dietImageError,
const DeepCollectionEquality().hash(_dailyScheduleErrors),
durationIndex,
dietStateIndex);
dietStateIndex,
published,
drafted);
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.
@@ -261,8 +423,16 @@ abstract class _CreateDietState implements CreateDietState {
required final String? errorMessage,
required final CreateDietModel dietData,
final List<List<DailySchedule>>? copiedDay,
final String? dietNameError,
final String? dietDescriptionError,
final String? dietDurationError,
final String? dietStateError,
final String? dietImageError,
required final List<List<String>> dailyScheduleErrors,
final int? durationIndex,
final int? dietStateIndex}) = _$CreateDietStateImpl;
final int? dietStateIndex,
final bool published,
final bool drafted}) = _$CreateDietStateImpl;
@override
bool get isLoading;
@@ -273,9 +443,25 @@ abstract class _CreateDietState implements CreateDietState {
@override
List<List<DailySchedule>>? get copiedDay;
@override
String? get dietNameError;
@override
String? get dietDescriptionError;
@override
String? get dietDurationError;
@override
String? get dietStateError;
@override
String? get dietImageError;
@override
List<List<String>> get dailyScheduleErrors;
@override
int? get durationIndex;
@override
int? get dietStateIndex;
@override
bool get published;
@override
bool get drafted;
/// Create a copy of CreateDietState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -297,7 +297,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
),
),
SizedBox(height: 12),
DietInfoRow(diet: widget.diet),
InfoRow(diet: widget.diet),
ExpandableText(
text: widget.diet.details,
),
@@ -306,6 +306,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.diet.author,
title: s.diet_author,
),
),
SizedBox(height: 16),

View File

@@ -9,6 +9,7 @@ import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/add_meal_widget.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
@@ -34,6 +35,7 @@ class _DayWidgetState extends State<DayWidget> {
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return MainBackgroundBouble(
hasError: state.dailyScheduleErrors[widget.index].where((element) => element.isNotEmpty).isNotEmpty,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -214,6 +216,10 @@ class _DayWidgetState extends State<DayWidget> {
),
],
),
if (state.dailyScheduleErrors[widget.index][_selectedMealIndex].isNotEmpty) ...[
SizedBox(height: 12),
CustomErrorWidget(error: state.dailyScheduleErrors[widget.index][_selectedMealIndex]),
],
SizedBox(height: 20),
],
),

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
@@ -36,28 +37,40 @@ class _DietDurationDropDownWidgetState extends State<DietDurationDropDownWidget>
final s = S.of(context);
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return CustomDropDownButton(
hint: Text(
s.choose_diet_duration,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDuration(durationDropDownTitles.indexOf(value));
}
},
items: durationDropDownTitles
.mapIndexed(
(index, value) => DropdownMenuItem(
child: Text(
durationDropDownTitles[index],
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
value: durationDropDownTitles[index],
),
)
.toList(),
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomDropDownButton(
hasError: state.dietDurationError != null,
hint: Text(
s.choose_diet_duration,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDuration(durationDropDownTitles.indexOf(value));
}
},
items: durationDropDownTitles
.mapIndexed(
(index, value) => DropdownMenuItem(
child: Text(
durationDropDownTitles[index],
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
value: durationDropDownTitles[index],
),
)
.toList(),
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
),
if (state.dietDurationError != null) ...[
SizedBox(
height: 4,
),
CustomErrorWidget(error: state.dietDurationError ?? ''),
],
],
);
},
);

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@@ -70,74 +71,79 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
final s = S.of(context);
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return GestureDetector(
onTap: () {
_showImagePickerOptions(s, context.read<CreateDietCubit>());
},
child: Container(
height: 240,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: state.dietData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.dietData.image?.path ?? ''))) : null,
color: appTheme.gray50,
return Column(
children: [
GestureDetector(
onTap: () {
_showImagePickerOptions(s, context.read<CreateDietCubit>());
},
child: Container(
height: 240,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: state.dietData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.dietData.image?.path ?? ''))) : null,
color: appTheme.gray50,
),
width: MediaQuery.sizeOf(context).width,
padding: state.dietData.image != null ? null : EdgeInsets.all(8),
child: state.dietData.image != null
? Padding(
padding: const EdgeInsets.only(top: 6, right: 6),
child: GestureDetector(
onTap: () {
context.read<CreateDietCubit>().setImage(null);
},
child: Align(
alignment: Alignment.topRight,
child: Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: appTheme.dividerColor,
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
color: Colors.grey,
size: 16,
),
),
),
),
)
: Stack(
children: [
DottedBorder(
stackFit: StackFit.expand,
dashPattern: [14, 14],
color: state.dietImageError != null ? appTheme.red : appTheme.dottedBorder,
borderType: BorderType.RRect,
radius: Radius.circular(12),
child: SizedBox.expand(),
),
Center(
child: Column(
children: [
SizedBox(
height: 48,
),
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
SizedBox(
height: 24,
),
Text(
s.add_cover,
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
),
],
),
),
],
),
),
),
width: MediaQuery.sizeOf(context).width,
padding: state.dietData.image != null ? null : EdgeInsets.all(8),
child: state.dietData.image != null
? Padding(
padding: const EdgeInsets.only(top: 6, right: 6),
child: GestureDetector(
onTap: () {
context.read<CreateDietCubit>().setImage(null);
},
child: Align(
alignment: Alignment.topRight,
child: Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: appTheme.dividerColor,
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
color: Colors.grey,
size: 16,
),
),
),
),
)
: Stack(
children: [
DottedBorder(
stackFit: StackFit.expand,
dashPattern: [14, 14],
color: appTheme.dottedBorder,
borderType: BorderType.RRect,
radius: Radius.circular(12),
child: SizedBox.expand(),
),
Center(
child: Column(
children: [
SizedBox(
height: 48,
),
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
SizedBox(
height: 24,
),
Text(
s.add_cover,
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
),
],
),
),
],
),
),
if (state.dietImageError != null) ...[SizedBox(height: 8), CustomErrorWidget(error: state.dietImageError ?? '')],
],
);
},
);

View File

@@ -0,0 +1,26 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class CustomErrorWidget extends StatelessWidget {
const CustomErrorWidget({super.key, required this.error});
final String error;
@override
Widget build(BuildContext context) {
return Row(
children: [
SvgPicture.asset('assets/icons/error_icon.svg', width: 14, height: 14),
SizedBox(
width: 2,
),
Expanded(
child: Text(
error,
style: TextStyles.textSMedium.copyWith(color: appTheme.red),
),
),
],
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -33,28 +34,40 @@ class _StatusDropDownWidgetState extends State<StatusDropDownWidget> {
final s = S.of(context);
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return CustomDropDownButton(
hint: Text(
s.diet_state,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDietState(dietStateTitles.indexOf(value));
}
},
items: dietStateTitles
.mapIndexed(
(index, value) => DropdownMenuItem(
child: Text(
dietStateTitles[index],
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
value: dietStateTitles[index],
),
)
.toList(),
value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0],
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomDropDownButton(
hasError: state.dietStateError != null,
hint: Text(
s.diet_state,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDietState(dietStateTitles.indexOf(value));
}
},
items: dietStateTitles
.mapIndexed(
(index, value) => DropdownMenuItem(
child: Text(
dietStateTitles[index],
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
value: dietStateTitles[index],
),
)
.toList(),
value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0],
),
if (state.dietStateError != null) ...[
SizedBox(
height: 4,
),
CustomErrorWidget(error: state.dietStateError ?? ''),
],
],
);
},
);

View File

@@ -7,11 +7,14 @@ class CreateDietRepository {
final CreateDietService createDietService;
CreateDietRepository({required this.createDietService});
Future<Diet> createDiet({
String? name,
int? duration,
String? state,
String? type,
Future<CreatedDiet> createDiet({
String? name,
int? duration,
String? state,
String? meatType,
String? carbType,
String? vegType,
String? lightType,
String? details,
File? image,
}) {
@@ -19,7 +22,10 @@ class CreateDietRepository {
name: name,
duration: duration,
state: state,
type: type,
meatType: meatType,
carbType: carbType,
vegType: vegType,
lightType: lightType,
details: details,
image: image,
);
@@ -29,9 +35,13 @@ class CreateDietRepository {
required int id,
required Map<String, dynamic> model,
}) {
return createDietService.createDietDailySchedule(
id: id,
model: model,
);
try {
return createDietService.createDietDailySchedule(
id: id,
model: model,
);
} catch (e) {
rethrow;
}
}
}

View File

@@ -12,11 +12,14 @@ abstract class CreateDietService {
@POST('/api/diets/create')
@MultiPart()
Future<Diet> createDiet({
Future<CreatedDiet> createDiet({
@Part(name: 'name') String? name,
@Part(name: 'duration') int? duration,
@Part(name: 'state') String? state,
@Part(name: 'type') String? type,
@Part(name: 'type[meat]') String? meatType,
@Part(name: 'type[carb]') String? carbType,
@Part(name: 'type[veg]') String? vegType,
@Part(name: 'type[light]') String? lightType,
@Part(name: 'details') String? details,
@Part(name: 'image') File? image,
});

View File

@@ -22,11 +22,14 @@ class _CreateDietService implements CreateDietService {
final ParseErrorLogger? errorLogger;
@override
Future<Diet> createDiet({
Future<CreatedDiet> createDiet({
String? name,
int? duration,
String? state,
String? type,
String? meatType,
String? carbType,
String? vegType,
String? lightType,
String? details,
File? image,
}) async {
@@ -53,10 +56,28 @@ class _CreateDietService implements CreateDietService {
state,
));
}
if (type != null) {
if (meatType != null) {
_data.fields.add(MapEntry(
'type',
type,
'type[meat]',
meatType,
));
}
if (carbType != null) {
_data.fields.add(MapEntry(
'type[carb]',
carbType,
));
}
if (vegType != null) {
_data.fields.add(MapEntry(
'type[veg]',
vegType,
));
}
if (lightType != null) {
_data.fields.add(MapEntry(
'type[light]',
lightType,
));
}
if (details != null) {
@@ -76,7 +97,7 @@ class _CreateDietService implements CreateDietService {
));
}
}
final _options = _setStreamType<Diet>(Options(
final _options = _setStreamType<CreatedDiet>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
@@ -94,9 +115,9 @@ class _CreateDietService implements CreateDietService {
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late Diet _value;
late CreatedDiet _value;
try {
_value = Diet.fromJson(_result.data!);
_value = CreatedDiet.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;