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;

View File

@@ -1,5 +1,5 @@
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/animated_option_button.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -25,7 +25,7 @@ class OptionsCountButton extends StatelessWidget {
child: Row(
children: [
Expanded(
child: _AnimatedOptionButton(
child: AnimatedOptionButton(
isSelected: selectedOption == null,
text: s.one_option,
onTap: () {
@@ -34,7 +34,7 @@ class OptionsCountButton extends StatelessWidget {
),
),
Expanded(
child: _AnimatedOptionButton(
child: AnimatedOptionButton(
isSelected: selectedOption != null,
text: s.three_options,
onTap: () {
@@ -50,33 +50,3 @@ class OptionsCountButton extends StatelessWidget {
}
}
class _AnimatedOptionButton extends StatelessWidget {
const _AnimatedOptionButton({required this.text, required this.isSelected, required this.onTap});
final String text;
final bool isSelected;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: isSelected ? appTheme.blueA400 : appTheme.whiteA700,
),
child: Center(
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 300),
style: TextStyles.textMSemibold.copyWith(
color: isSelected ? appTheme.whiteA700 : appTheme.black,
),
child: Text(text),
),
),
),
);
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:io';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
@@ -155,20 +156,25 @@ class DiaryCubit extends Cubit<DiaryState> {
await fetchDiary();
}
Future<GlossaryModerationModel> sendProductToModeration({
Future<GlossaryModerationModel?> sendProductToModeration({
required String name,
required double energy,
required double proteins,
required double fats,
required double carbohydrates,
}) async {
return await diaryRepository.sendProductToModeration(
name: name,
energy: energy,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
);
try {
return await diaryRepository.sendProductToModeration(
name: name,
energy: energy,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
);
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
}
return null;
}
fillTime({

View File

@@ -271,7 +271,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
fats: double.parse(fatsController.text),
carbohydrates: double.parse(carbohydratesController.text),
))
.glossary;
?.glossary;
setState(() {
glossaryState = GlossaryState.success;
});
@@ -279,7 +279,6 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 8),
width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),

View File

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

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -8,11 +7,12 @@ import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
class AuthorCard extends StatelessWidget {
const AuthorCard({super.key, required this.author});
const AuthorCard({super.key, required this.author, required this.title});
final Author author;
final String title;
@override
Widget build(BuildContext context) {
final s = S.of(context);
// final s = S.of(context);
return Container(
width: MediaQuery.sizeOf(context).width,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.cardBackground),
@@ -42,7 +42,7 @@ class AuthorCard extends StatelessWidget {
),
),
title: Text(
s.diet_author,
title,
style: TextStyles.textSSemibold.copyWith(
color: appTheme.gray,
),
@@ -57,18 +57,19 @@ class AuthorCard extends StatelessWidget {
),
),
SizedBox(width: 10),
InfoBouble(
radius: 12,
backgroundColor: appTheme.whiteA700,
children: [
Text(
'128-й в рейтинге',
style: TextStyles.textMMediumAuto.copyWith(
color: appTheme.blueA400,
if (author.authorPlaceInRating != null)
InfoBouble(
radius: 12,
backgroundColor: appTheme.whiteA700,
children: [
Text(
'${author.authorPlaceInRating}-й в рейтинге',
style: TextStyles.textMMediumAuto.copyWith(
color: appTheme.blueA400,
),
),
),
],
),
],
),
],
),
],

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -8,9 +9,10 @@ import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
class TypeBadges extends StatefulWidget {
const TypeBadges({super.key, required this.type, required this.disableDescriptionOfTypes});
const TypeBadges({super.key, this.type, required this.disableDescriptionOfTypes, this.planType});
final DietType type;
final DietType? type;
final PlanType? planType;
final bool disableDescriptionOfTypes;
@override
State<TypeBadges> createState() => _TypeBadgesState();
@@ -18,12 +20,11 @@ class TypeBadges extends StatefulWidget {
class _TypeBadgesState extends State<TypeBadges> {
final List<bool> overlaysVisibilityState = [false, false, false, false];
final List<(String, Widget)> badgeSource = [];
@override
Widget build(BuildContext context) {
final s = S.of(context);
final List<(String, Widget)> badgeSource = [];
if (widget.type.light == true) {
void fillBadges(S s) {
badgeSource.clear();
if (widget.type?.light == true) {
badgeSource.add(
(
'assets/icons/cocktail.svg',
@@ -36,7 +37,7 @@ class _TypeBadgesState extends State<TypeBadges> {
),
);
}
if (widget.type.veg == true) {
if (widget.type?.veg == true) {
badgeSource.add(
(
'assets/icons/broccoli.svg',
@@ -49,7 +50,7 @@ class _TypeBadgesState extends State<TypeBadges> {
),
);
}
if (widget.type.carb == true) {
if (widget.type?.carb == true) {
badgeSource.add(
(
'assets/icons/bread.svg',
@@ -62,7 +63,7 @@ class _TypeBadgesState extends State<TypeBadges> {
),
);
}
if (widget.type.meat == true) {
if (widget.type?.meat == true) {
badgeSource.add(
(
'assets/icons/meat2.svg',
@@ -75,6 +76,66 @@ class _TypeBadgesState extends State<TypeBadges> {
),
);
}
if (widget.planType?.slim == true) {
badgeSource.add(
(
'assets/icons/slim.png',
_InfoOverlayEntry(
onClose: closeOverlay,
assetPath: 'assets/icons/slim.png',
title: s.slim_type,
description: s.slim_type_desc,
)
),
);
}
if (widget.planType?.fit == true) {
badgeSource.add(
(
'assets/icons/fitness.png',
_InfoOverlayEntry(
onClose: closeOverlay,
assetPath: 'assets/icons/fitness.png',
title: s.fit_type,
description: s.fit_type_desc,
)
),
);
}
if (widget.planType?.wellness == true) {
badgeSource.add(
(
'assets/icons/yoga-pose.png',
_InfoOverlayEntry(
onClose: closeOverlay,
assetPath: 'assets/icons/yoga-pose.png',
title: s.wellness_type,
description: s.wellness_type_desc,
)
),
);
}
if (widget.planType?.behavior == true) {
badgeSource.add(
(
'assets/icons/diet.png',
_InfoOverlayEntry(
onClose: closeOverlay,
assetPath: 'assets/icons/diet.png',
title: s.eating_behavior_type,
description: s.eating_behavior_type_desc,
)
),
);
}
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
fillBadges(s);
return Row(
children: List.generate(
badgeSource.length,
@@ -107,11 +168,13 @@ class _TypeBadgesState extends State<TypeBadges> {
},
child: Padding(
padding: EdgeInsets.only(right: index != badgeSource.length - 1 ? 8.0 : 0),
child: SvgPicture.asset(
badgeSource[index].$1,
width: 16,
height: 16,
),
child: badgeSource[index].$1.endsWith('.png')
? Image.asset(badgeSource[index].$1, width: 20, height: 20)
: SvgPicture.asset(
badgeSource[index].$1,
width: 16,
height: 16,
),
),
),
),
@@ -150,11 +213,13 @@ class _InfoOverlayEntry extends StatelessWidget {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SvgPicture.asset(
assetPath,
width: 20,
height: 20,
),
assetPath.endsWith('.png')
? Image.asset(assetPath, width: 20, height: 20)
: SvgPicture.asset(
assetPath,
width: 20,
height: 20,
),
SizedBox(width: 8),
Expanded(
child: Column(
@@ -175,7 +240,6 @@ class _InfoOverlayEntry extends StatelessWidget {
style: TextStyles.textXSSemibold.copyWith(
decoration: TextDecoration.none,
color: appTheme.black,
),
),
],

View File

@@ -39,6 +39,23 @@ class Diet with _$Diet {
factory Diet.fromJson(Map<String, dynamic> json) => _$DietFromJson(json);
}
@freezed
class CreatedDiet with _$CreatedDiet {
const factory CreatedDiet({
@JsonKey(name: "id") required int id,
}) = _CreatedDiet;
factory CreatedDiet.fromJson(Map<String, dynamic> json) => _$CreatedDietFromJson(json);
}
int parseDuration(dynamic duration) {
if (duration is String) {
return int.parse(duration);
}else {
return duration;
}
}
@freezed
class Author with _$Author {
const factory Author({
@@ -46,7 +63,8 @@ class Author with _$Author {
@JsonKey(name: "author_name") required String authorName,
@JsonKey(name: "author_profile_icon") required String authorProfileIcon,
@JsonKey(name: "author_is_me") required bool authorIsMe,
@JsonKey(name: "is_entity_subscribed_by_me") required bool isEntitySubscribedByMe,
@JsonKey(name: "is_entity_subscribed_by_me") bool? isEntitySubscribedByMe,
@JsonKey(name: "author_place_in_rating") int? authorPlaceInRating,
}) = _Author;
factory Author.fromJson(Map<String, dynamic> json) => _$AuthorFromJson(json);
@@ -60,7 +78,7 @@ class DailySchedule with _$DailySchedule {
@JsonKey(name: "portion") required Portion portion,
@JsonKey(name: "quantity") required int quantity,
@JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition,
@JsonKey(name: 'product_nutritional_value_id', includeFromJson: false, includeIfNull: false) String? productNutritionalValueId,
@JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: false, includeIfNull: false) String? productNutritionalValueId,
}) = _DailySchedule;
factory DailySchedule.fromJson(Map<String, dynamic> json) => _$DailyScheduleFromJson(json);
@@ -79,6 +97,7 @@ final portionValues = EnumValues({"grams": Portion.GRAMS, "milliliters": Portion
@freezed
class DietType with _$DietType {
const DietType._();
const factory DietType({
@JsonKey(name: "light") bool? light,
@JsonKey(name: "veg") bool? veg,
@@ -87,6 +106,10 @@ class DietType with _$DietType {
}) = _DietType;
factory DietType.fromJson(Map<String, dynamic> json) => _$DietTypeFromJson(json);
bool hasAtLeastOneType() {
return light ?? false || (veg ?? false) || (carb ?? false) || (meat ?? false);
}
}
class EnumValues<T> {

View File

@@ -798,6 +798,159 @@ abstract class _Diet implements Diet {
throw _privateConstructorUsedError;
}
CreatedDiet _$CreatedDietFromJson(Map<String, dynamic> json) {
return _CreatedDiet.fromJson(json);
}
/// @nodoc
mixin _$CreatedDiet {
@JsonKey(name: "id")
int get id => throw _privateConstructorUsedError;
/// Serializes this CreatedDiet to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of CreatedDiet
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$CreatedDietCopyWith<CreatedDiet> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CreatedDietCopyWith<$Res> {
factory $CreatedDietCopyWith(
CreatedDiet value, $Res Function(CreatedDiet) then) =
_$CreatedDietCopyWithImpl<$Res, CreatedDiet>;
@useResult
$Res call({@JsonKey(name: "id") int id});
}
/// @nodoc
class _$CreatedDietCopyWithImpl<$Res, $Val extends CreatedDiet>
implements $CreatedDietCopyWith<$Res> {
_$CreatedDietCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CreatedDiet
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$CreatedDietImplCopyWith<$Res>
implements $CreatedDietCopyWith<$Res> {
factory _$$CreatedDietImplCopyWith(
_$CreatedDietImpl value, $Res Function(_$CreatedDietImpl) then) =
__$$CreatedDietImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({@JsonKey(name: "id") int id});
}
/// @nodoc
class __$$CreatedDietImplCopyWithImpl<$Res>
extends _$CreatedDietCopyWithImpl<$Res, _$CreatedDietImpl>
implements _$$CreatedDietImplCopyWith<$Res> {
__$$CreatedDietImplCopyWithImpl(
_$CreatedDietImpl _value, $Res Function(_$CreatedDietImpl) _then)
: super(_value, _then);
/// Create a copy of CreatedDiet
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
}) {
return _then(_$CreatedDietImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$CreatedDietImpl implements _CreatedDiet {
const _$CreatedDietImpl({@JsonKey(name: "id") required this.id});
factory _$CreatedDietImpl.fromJson(Map<String, dynamic> json) =>
_$$CreatedDietImplFromJson(json);
@override
@JsonKey(name: "id")
final int id;
@override
String toString() {
return 'CreatedDiet(id: $id)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CreatedDietImpl &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id);
/// Create a copy of CreatedDiet
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CreatedDietImplCopyWith<_$CreatedDietImpl> get copyWith =>
__$$CreatedDietImplCopyWithImpl<_$CreatedDietImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$CreatedDietImplToJson(
this,
);
}
}
abstract class _CreatedDiet implements CreatedDiet {
const factory _CreatedDiet({@JsonKey(name: "id") required final int id}) =
_$CreatedDietImpl;
factory _CreatedDiet.fromJson(Map<String, dynamic> json) =
_$CreatedDietImpl.fromJson;
@override
@JsonKey(name: "id")
int get id;
/// Create a copy of CreatedDiet
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CreatedDietImplCopyWith<_$CreatedDietImpl> get copyWith =>
throw _privateConstructorUsedError;
}
Author _$AuthorFromJson(Map<String, dynamic> json) {
return _Author.fromJson(json);
}
@@ -813,7 +966,9 @@ mixin _$Author {
@JsonKey(name: "author_is_me")
bool get authorIsMe => throw _privateConstructorUsedError;
@JsonKey(name: "is_entity_subscribed_by_me")
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
bool? get isEntitySubscribedByMe => throw _privateConstructorUsedError;
@JsonKey(name: "author_place_in_rating")
int? get authorPlaceInRating => throw _privateConstructorUsedError;
/// Serializes this Author to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -834,8 +989,8 @@ abstract class $AuthorCopyWith<$Res> {
@JsonKey(name: "author_name") String authorName,
@JsonKey(name: "author_profile_icon") String authorProfileIcon,
@JsonKey(name: "author_is_me") bool authorIsMe,
@JsonKey(name: "is_entity_subscribed_by_me")
bool isEntitySubscribedByMe});
@JsonKey(name: "is_entity_subscribed_by_me") bool? isEntitySubscribedByMe,
@JsonKey(name: "author_place_in_rating") int? authorPlaceInRating});
}
/// @nodoc
@@ -857,7 +1012,8 @@ class _$AuthorCopyWithImpl<$Res, $Val extends Author>
Object? authorName = null,
Object? authorProfileIcon = null,
Object? authorIsMe = null,
Object? isEntitySubscribedByMe = null,
Object? isEntitySubscribedByMe = freezed,
Object? authorPlaceInRating = freezed,
}) {
return _then(_value.copyWith(
authorSlug: null == authorSlug
@@ -876,10 +1032,14 @@ class _$AuthorCopyWithImpl<$Res, $Val extends Author>
? _value.authorIsMe
: authorIsMe // ignore: cast_nullable_to_non_nullable
as bool,
isEntitySubscribedByMe: null == isEntitySubscribedByMe
isEntitySubscribedByMe: freezed == isEntitySubscribedByMe
? _value.isEntitySubscribedByMe
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
as bool,
as bool?,
authorPlaceInRating: freezed == authorPlaceInRating
? _value.authorPlaceInRating
: authorPlaceInRating // ignore: cast_nullable_to_non_nullable
as int?,
) as $Val);
}
}
@@ -896,8 +1056,8 @@ abstract class _$$AuthorImplCopyWith<$Res> implements $AuthorCopyWith<$Res> {
@JsonKey(name: "author_name") String authorName,
@JsonKey(name: "author_profile_icon") String authorProfileIcon,
@JsonKey(name: "author_is_me") bool authorIsMe,
@JsonKey(name: "is_entity_subscribed_by_me")
bool isEntitySubscribedByMe});
@JsonKey(name: "is_entity_subscribed_by_me") bool? isEntitySubscribedByMe,
@JsonKey(name: "author_place_in_rating") int? authorPlaceInRating});
}
/// @nodoc
@@ -917,7 +1077,8 @@ class __$$AuthorImplCopyWithImpl<$Res>
Object? authorName = null,
Object? authorProfileIcon = null,
Object? authorIsMe = null,
Object? isEntitySubscribedByMe = null,
Object? isEntitySubscribedByMe = freezed,
Object? authorPlaceInRating = freezed,
}) {
return _then(_$AuthorImpl(
authorSlug: null == authorSlug
@@ -936,10 +1097,14 @@ class __$$AuthorImplCopyWithImpl<$Res>
? _value.authorIsMe
: authorIsMe // ignore: cast_nullable_to_non_nullable
as bool,
isEntitySubscribedByMe: null == isEntitySubscribedByMe
isEntitySubscribedByMe: freezed == isEntitySubscribedByMe
? _value.isEntitySubscribedByMe
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
as bool,
as bool?,
authorPlaceInRating: freezed == authorPlaceInRating
? _value.authorPlaceInRating
: authorPlaceInRating // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
@@ -952,8 +1117,8 @@ class _$AuthorImpl implements _Author {
@JsonKey(name: "author_name") required this.authorName,
@JsonKey(name: "author_profile_icon") required this.authorProfileIcon,
@JsonKey(name: "author_is_me") required this.authorIsMe,
@JsonKey(name: "is_entity_subscribed_by_me")
required this.isEntitySubscribedByMe});
@JsonKey(name: "is_entity_subscribed_by_me") this.isEntitySubscribedByMe,
@JsonKey(name: "author_place_in_rating") this.authorPlaceInRating});
factory _$AuthorImpl.fromJson(Map<String, dynamic> json) =>
_$$AuthorImplFromJson(json);
@@ -972,11 +1137,14 @@ class _$AuthorImpl implements _Author {
final bool authorIsMe;
@override
@JsonKey(name: "is_entity_subscribed_by_me")
final bool isEntitySubscribedByMe;
final bool? isEntitySubscribedByMe;
@override
@JsonKey(name: "author_place_in_rating")
final int? authorPlaceInRating;
@override
String toString() {
return 'Author(authorSlug: $authorSlug, authorName: $authorName, authorProfileIcon: $authorProfileIcon, authorIsMe: $authorIsMe, isEntitySubscribedByMe: $isEntitySubscribedByMe)';
return 'Author(authorSlug: $authorSlug, authorName: $authorName, authorProfileIcon: $authorProfileIcon, authorIsMe: $authorIsMe, isEntitySubscribedByMe: $isEntitySubscribedByMe, authorPlaceInRating: $authorPlaceInRating)';
}
@override
@@ -993,13 +1161,21 @@ class _$AuthorImpl implements _Author {
(identical(other.authorIsMe, authorIsMe) ||
other.authorIsMe == authorIsMe) &&
(identical(other.isEntitySubscribedByMe, isEntitySubscribedByMe) ||
other.isEntitySubscribedByMe == isEntitySubscribedByMe));
other.isEntitySubscribedByMe == isEntitySubscribedByMe) &&
(identical(other.authorPlaceInRating, authorPlaceInRating) ||
other.authorPlaceInRating == authorPlaceInRating));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, authorSlug, authorName,
authorProfileIcon, authorIsMe, isEntitySubscribedByMe);
int get hashCode => Object.hash(
runtimeType,
authorSlug,
authorName,
authorProfileIcon,
authorIsMe,
isEntitySubscribedByMe,
authorPlaceInRating);
/// Create a copy of Author
/// with the given fields replaced by the non-null parameter values.
@@ -1025,7 +1201,9 @@ abstract class _Author implements Author {
required final String authorProfileIcon,
@JsonKey(name: "author_is_me") required final bool authorIsMe,
@JsonKey(name: "is_entity_subscribed_by_me")
required final bool isEntitySubscribedByMe}) = _$AuthorImpl;
final bool? isEntitySubscribedByMe,
@JsonKey(name: "author_place_in_rating")
final int? authorPlaceInRating}) = _$AuthorImpl;
factory _Author.fromJson(Map<String, dynamic> json) = _$AuthorImpl.fromJson;
@@ -1043,7 +1221,10 @@ abstract class _Author implements Author {
bool get authorIsMe;
@override
@JsonKey(name: "is_entity_subscribed_by_me")
bool get isEntitySubscribedByMe;
bool? get isEntitySubscribedByMe;
@override
@JsonKey(name: "author_place_in_rating")
int? get authorPlaceInRating;
/// Create a copy of Author
/// with the given fields replaced by the non-null parameter values.
@@ -1071,6 +1252,7 @@ mixin _$DailySchedule {
DailyNutrition? get dailyNutrition => throw _privateConstructorUsedError;
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
String? get productNutritionalValueId => throw _privateConstructorUsedError;
@@ -1100,6 +1282,7 @@ abstract class $DailyScheduleCopyWith<$Res> {
DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
String? productNutritionalValueId});
@@ -1189,6 +1372,7 @@ abstract class _$$DailyScheduleImplCopyWith<$Res>
DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
String? productNutritionalValueId});
@@ -1258,6 +1442,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
this.dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
this.productNutritionalValueId});
@@ -1283,6 +1468,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
@override
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
final String? productNutritionalValueId;
@@ -1340,6 +1526,7 @@ abstract class _DailySchedule implements DailySchedule {
final DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
final String? productNutritionalValueId}) = _$DailyScheduleImpl;
@@ -1365,6 +1552,7 @@ abstract class _DailySchedule implements DailySchedule {
@override
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
includeFromJson: false,
includeIfNull: false)
String? get productNutritionalValueId;
@@ -1511,12 +1699,13 @@ class __$$DietTypeImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$DietTypeImpl implements _DietType {
class _$DietTypeImpl extends _DietType {
const _$DietTypeImpl(
{@JsonKey(name: "light") this.light,
@JsonKey(name: "veg") this.veg,
@JsonKey(name: "carb") this.carb,
@JsonKey(name: "meat") this.meat});
@JsonKey(name: "meat") this.meat})
: super._();
factory _$DietTypeImpl.fromJson(Map<String, dynamic> json) =>
_$$DietTypeImplFromJson(json);
@@ -1570,12 +1759,13 @@ class _$DietTypeImpl implements _DietType {
}
}
abstract class _DietType implements DietType {
abstract class _DietType extends DietType {
const factory _DietType(
{@JsonKey(name: "light") final bool? light,
@JsonKey(name: "veg") final bool? veg,
@JsonKey(name: "carb") final bool? carb,
@JsonKey(name: "meat") final bool? meat}) = _$DietTypeImpl;
const _DietType._() : super._();
factory _DietType.fromJson(Map<String, dynamic> json) =
_$DietTypeImpl.fromJson;

View File

@@ -69,12 +69,23 @@ Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) =>
'daily_nutrition': instance.dailyNutrition,
};
_$CreatedDietImpl _$$CreatedDietImplFromJson(Map<String, dynamic> json) =>
_$CreatedDietImpl(
id: (json['id'] as num).toInt(),
);
Map<String, dynamic> _$$CreatedDietImplToJson(_$CreatedDietImpl instance) =>
<String, dynamic>{
'id': instance.id,
};
_$AuthorImpl _$$AuthorImplFromJson(Map<String, dynamic> json) => _$AuthorImpl(
authorSlug: json['author_slug'] as String,
authorName: json['author_name'] as String,
authorProfileIcon: json['author_profile_icon'] as String,
authorIsMe: json['author_is_me'] as bool,
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool,
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool?,
authorPlaceInRating: (json['author_place_in_rating'] as num?)?.toInt(),
);
Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
@@ -84,6 +95,7 @@ Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
'author_profile_icon': instance.authorProfileIcon,
'author_is_me': instance.authorIsMe,
'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe,
'author_place_in_rating': instance.authorPlaceInRating,
};
_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) =>
@@ -99,6 +111,8 @@ Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) =>
'name': instance.name,
'portion': _$PortionEnumMap[instance.portion]!,
'quantity': instance.quantity,
if (instance.productNutritionalValueId case final value?)
'product_nutritional_value_id': value,
};
const _$PortionEnumMap = {

View File

@@ -21,6 +21,7 @@ class PlanPaginationModel with _$PlanPaginationModel {
class PlanModel with _$PlanModel {
const factory PlanModel({
@JsonKey(name: "id") required int id,
@JsonKey(name: "slug") required String slug,
@JsonKey(name: "name") required String name,
@JsonKey(name: "image") required String image,
@JsonKey(name: "duration") required int duration,
@@ -28,13 +29,14 @@ class PlanModel with _$PlanModel {
@JsonKey(name: "type") required PlanType type,
@JsonKey(name: "details") required String details,
@JsonKey(name: "packets_count") required int packetsCount,
@JsonKey(name: "packet1_options") dynamic packet1Options,
@JsonKey(name: "packet2_options") dynamic packet2Options,
@JsonKey(name: "packet3_options") dynamic packet3Options,
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
@JsonKey(name: "packet2_options") PacketOptions? packet2Options,
@JsonKey(name: "packet3_options") PacketOptions? packet3Options,
@JsonKey(name: "author") required Author author,
@JsonKey(name: "total_likes") required int totalLikes,
@JsonKey(name: "total_views") required int totalViews,
@JsonKey(name: "total_comments") required int totalComments,
@JsonKey(name: "total_comments") required int totalComments,
@JsonKey(name: "is_favorite") required bool isFavorite,
}) = _PlanModel;
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
@@ -56,12 +58,17 @@ class PacketOptions with _$PacketOptions {
@freezed
class PlanType with _$PlanType {
const PlanType._();
const factory PlanType({
@JsonKey(name: "fit") required bool fit,
@JsonKey(name: "slim") required bool slim,
@JsonKey(name: "behavior") required bool behavior,
@JsonKey(name: "wellness") required bool wellness,
@JsonKey(name: "fit") required bool? fit,
@JsonKey(name: "slim") required bool? slim,
@JsonKey(name: "behavior") required bool? behavior,
@JsonKey(name: "wellness") required bool? wellness,
}) = _PlanType;
factory PlanType.fromJson(Map<String, dynamic> json) => _$PlanTypeFromJson(json);
bool hasAtLeastOneType() {
return (fit ?? false) || (slim ?? false) || (behavior ?? false) || (wellness ?? false);
}
}

View File

@@ -262,6 +262,8 @@ PlanModel _$PlanModelFromJson(Map<String, dynamic> json) {
mixin _$PlanModel {
@JsonKey(name: "id")
int get id => throw _privateConstructorUsedError;
@JsonKey(name: "slug")
String get slug => throw _privateConstructorUsedError;
@JsonKey(name: "name")
String get name => throw _privateConstructorUsedError;
@JsonKey(name: "image")
@@ -277,11 +279,11 @@ mixin _$PlanModel {
@JsonKey(name: "packets_count")
int get packetsCount => throw _privateConstructorUsedError;
@JsonKey(name: "packet1_options")
dynamic get packet1Options => throw _privateConstructorUsedError;
PacketOptions? get packet1Options => throw _privateConstructorUsedError;
@JsonKey(name: "packet2_options")
dynamic get packet2Options => throw _privateConstructorUsedError;
PacketOptions? get packet2Options => throw _privateConstructorUsedError;
@JsonKey(name: "packet3_options")
dynamic get packet3Options => throw _privateConstructorUsedError;
PacketOptions? get packet3Options => throw _privateConstructorUsedError;
@JsonKey(name: "author")
Author get author => throw _privateConstructorUsedError;
@JsonKey(name: "total_likes")
@@ -290,6 +292,8 @@ mixin _$PlanModel {
int get totalViews => throw _privateConstructorUsedError;
@JsonKey(name: "total_comments")
int get totalComments => throw _privateConstructorUsedError;
@JsonKey(name: "is_favorite")
bool get isFavorite => throw _privateConstructorUsedError;
/// Serializes this PlanModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -308,6 +312,7 @@ abstract class $PlanModelCopyWith<$Res> {
@useResult
$Res call(
{@JsonKey(name: "id") int id,
@JsonKey(name: "slug") String slug,
@JsonKey(name: "name") String name,
@JsonKey(name: "image") String image,
@JsonKey(name: "duration") int duration,
@@ -315,15 +320,19 @@ abstract class $PlanModelCopyWith<$Res> {
@JsonKey(name: "type") PlanType type,
@JsonKey(name: "details") String details,
@JsonKey(name: "packets_count") int packetsCount,
@JsonKey(name: "packet1_options") dynamic packet1Options,
@JsonKey(name: "packet2_options") dynamic packet2Options,
@JsonKey(name: "packet3_options") dynamic packet3Options,
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
@JsonKey(name: "packet2_options") PacketOptions? packet2Options,
@JsonKey(name: "packet3_options") PacketOptions? packet3Options,
@JsonKey(name: "author") Author author,
@JsonKey(name: "total_likes") int totalLikes,
@JsonKey(name: "total_views") int totalViews,
@JsonKey(name: "total_comments") int totalComments});
@JsonKey(name: "total_comments") int totalComments,
@JsonKey(name: "is_favorite") bool isFavorite});
$PlanTypeCopyWith<$Res> get type;
$PacketOptionsCopyWith<$Res>? get packet1Options;
$PacketOptionsCopyWith<$Res>? get packet2Options;
$PacketOptionsCopyWith<$Res>? get packet3Options;
$AuthorCopyWith<$Res> get author;
}
@@ -343,6 +352,7 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
@override
$Res call({
Object? id = null,
Object? slug = null,
Object? name = null,
Object? image = null,
Object? duration = null,
@@ -357,12 +367,17 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
Object? totalLikes = null,
Object? totalViews = null,
Object? totalComments = null,
Object? isFavorite = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
@@ -394,15 +409,15 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
packet1Options: freezed == packet1Options
? _value.packet1Options
: packet1Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
packet2Options: freezed == packet2Options
? _value.packet2Options
: packet2Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
packet3Options: freezed == packet3Options
? _value.packet3Options
: packet3Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
@@ -419,6 +434,10 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
? _value.totalComments
: totalComments // ignore: cast_nullable_to_non_nullable
as int,
isFavorite: null == isFavorite
? _value.isFavorite
: isFavorite // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -432,6 +451,48 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
});
}
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PacketOptionsCopyWith<$Res>? get packet1Options {
if (_value.packet1Options == null) {
return null;
}
return $PacketOptionsCopyWith<$Res>(_value.packet1Options!, (value) {
return _then(_value.copyWith(packet1Options: value) as $Val);
});
}
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PacketOptionsCopyWith<$Res>? get packet2Options {
if (_value.packet2Options == null) {
return null;
}
return $PacketOptionsCopyWith<$Res>(_value.packet2Options!, (value) {
return _then(_value.copyWith(packet2Options: value) as $Val);
});
}
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PacketOptionsCopyWith<$Res>? get packet3Options {
if (_value.packet3Options == null) {
return null;
}
return $PacketOptionsCopyWith<$Res>(_value.packet3Options!, (value) {
return _then(_value.copyWith(packet3Options: value) as $Val);
});
}
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@override
@@ -453,6 +514,7 @@ abstract class _$$PlanModelImplCopyWith<$Res>
@useResult
$Res call(
{@JsonKey(name: "id") int id,
@JsonKey(name: "slug") String slug,
@JsonKey(name: "name") String name,
@JsonKey(name: "image") String image,
@JsonKey(name: "duration") int duration,
@@ -460,17 +522,24 @@ abstract class _$$PlanModelImplCopyWith<$Res>
@JsonKey(name: "type") PlanType type,
@JsonKey(name: "details") String details,
@JsonKey(name: "packets_count") int packetsCount,
@JsonKey(name: "packet1_options") dynamic packet1Options,
@JsonKey(name: "packet2_options") dynamic packet2Options,
@JsonKey(name: "packet3_options") dynamic packet3Options,
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
@JsonKey(name: "packet2_options") PacketOptions? packet2Options,
@JsonKey(name: "packet3_options") PacketOptions? packet3Options,
@JsonKey(name: "author") Author author,
@JsonKey(name: "total_likes") int totalLikes,
@JsonKey(name: "total_views") int totalViews,
@JsonKey(name: "total_comments") int totalComments});
@JsonKey(name: "total_comments") int totalComments,
@JsonKey(name: "is_favorite") bool isFavorite});
@override
$PlanTypeCopyWith<$Res> get type;
@override
$PacketOptionsCopyWith<$Res>? get packet1Options;
@override
$PacketOptionsCopyWith<$Res>? get packet2Options;
@override
$PacketOptionsCopyWith<$Res>? get packet3Options;
@override
$AuthorCopyWith<$Res> get author;
}
@@ -488,6 +557,7 @@ class __$$PlanModelImplCopyWithImpl<$Res>
@override
$Res call({
Object? id = null,
Object? slug = null,
Object? name = null,
Object? image = null,
Object? duration = null,
@@ -502,12 +572,17 @@ class __$$PlanModelImplCopyWithImpl<$Res>
Object? totalLikes = null,
Object? totalViews = null,
Object? totalComments = null,
Object? isFavorite = null,
}) {
return _then(_$PlanModelImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
@@ -539,15 +614,15 @@ class __$$PlanModelImplCopyWithImpl<$Res>
packet1Options: freezed == packet1Options
? _value.packet1Options
: packet1Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
packet2Options: freezed == packet2Options
? _value.packet2Options
: packet2Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
packet3Options: freezed == packet3Options
? _value.packet3Options
: packet3Options // ignore: cast_nullable_to_non_nullable
as dynamic,
as PacketOptions?,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
@@ -564,6 +639,10 @@ class __$$PlanModelImplCopyWithImpl<$Res>
? _value.totalComments
: totalComments // ignore: cast_nullable_to_non_nullable
as int,
isFavorite: null == isFavorite
? _value.isFavorite
: isFavorite // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -573,6 +652,7 @@ class __$$PlanModelImplCopyWithImpl<$Res>
class _$PlanModelImpl implements _PlanModel {
const _$PlanModelImpl(
{@JsonKey(name: "id") required this.id,
@JsonKey(name: "slug") required this.slug,
@JsonKey(name: "name") required this.name,
@JsonKey(name: "image") required this.image,
@JsonKey(name: "duration") required this.duration,
@@ -586,7 +666,8 @@ class _$PlanModelImpl implements _PlanModel {
@JsonKey(name: "author") required this.author,
@JsonKey(name: "total_likes") required this.totalLikes,
@JsonKey(name: "total_views") required this.totalViews,
@JsonKey(name: "total_comments") required this.totalComments});
@JsonKey(name: "total_comments") required this.totalComments,
@JsonKey(name: "is_favorite") required this.isFavorite});
factory _$PlanModelImpl.fromJson(Map<String, dynamic> json) =>
_$$PlanModelImplFromJson(json);
@@ -595,6 +676,9 @@ class _$PlanModelImpl implements _PlanModel {
@JsonKey(name: "id")
final int id;
@override
@JsonKey(name: "slug")
final String slug;
@override
@JsonKey(name: "name")
final String name;
@override
@@ -617,13 +701,13 @@ class _$PlanModelImpl implements _PlanModel {
final int packetsCount;
@override
@JsonKey(name: "packet1_options")
final dynamic packet1Options;
final PacketOptions? packet1Options;
@override
@JsonKey(name: "packet2_options")
final dynamic packet2Options;
final PacketOptions? packet2Options;
@override
@JsonKey(name: "packet3_options")
final dynamic packet3Options;
final PacketOptions? packet3Options;
@override
@JsonKey(name: "author")
final Author author;
@@ -636,10 +720,13 @@ class _$PlanModelImpl implements _PlanModel {
@override
@JsonKey(name: "total_comments")
final int totalComments;
@override
@JsonKey(name: "is_favorite")
final bool isFavorite;
@override
String toString() {
return 'PlanModel(id: $id, name: $name, image: $image, duration: $duration, state: $state, type: $type, details: $details, packetsCount: $packetsCount, packet1Options: $packet1Options, packet2Options: $packet2Options, packet3Options: $packet3Options, author: $author, totalLikes: $totalLikes, totalViews: $totalViews, totalComments: $totalComments)';
return 'PlanModel(id: $id, slug: $slug, name: $name, image: $image, duration: $duration, state: $state, type: $type, details: $details, packetsCount: $packetsCount, packet1Options: $packet1Options, packet2Options: $packet2Options, packet3Options: $packet3Options, author: $author, totalLikes: $totalLikes, totalViews: $totalViews, totalComments: $totalComments, isFavorite: $isFavorite)';
}
@override
@@ -648,6 +735,7 @@ class _$PlanModelImpl implements _PlanModel {
(other.runtimeType == runtimeType &&
other is _$PlanModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.slug, slug) || other.slug == slug) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.image, image) || other.image == image) &&
(identical(other.duration, duration) ||
@@ -657,19 +745,21 @@ class _$PlanModelImpl implements _PlanModel {
(identical(other.details, details) || other.details == details) &&
(identical(other.packetsCount, packetsCount) ||
other.packetsCount == packetsCount) &&
const DeepCollectionEquality()
.equals(other.packet1Options, packet1Options) &&
const DeepCollectionEquality()
.equals(other.packet2Options, packet2Options) &&
const DeepCollectionEquality()
.equals(other.packet3Options, packet3Options) &&
(identical(other.packet1Options, packet1Options) ||
other.packet1Options == packet1Options) &&
(identical(other.packet2Options, packet2Options) ||
other.packet2Options == packet2Options) &&
(identical(other.packet3Options, packet3Options) ||
other.packet3Options == packet3Options) &&
(identical(other.author, author) || other.author == author) &&
(identical(other.totalLikes, totalLikes) ||
other.totalLikes == totalLikes) &&
(identical(other.totalViews, totalViews) ||
other.totalViews == totalViews) &&
(identical(other.totalComments, totalComments) ||
other.totalComments == totalComments));
other.totalComments == totalComments) &&
(identical(other.isFavorite, isFavorite) ||
other.isFavorite == isFavorite));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -677,6 +767,7 @@ class _$PlanModelImpl implements _PlanModel {
int get hashCode => Object.hash(
runtimeType,
id,
slug,
name,
image,
duration,
@@ -684,13 +775,14 @@ class _$PlanModelImpl implements _PlanModel {
type,
details,
packetsCount,
const DeepCollectionEquality().hash(packet1Options),
const DeepCollectionEquality().hash(packet2Options),
const DeepCollectionEquality().hash(packet3Options),
packet1Options,
packet2Options,
packet3Options,
author,
totalLikes,
totalViews,
totalComments);
totalComments,
isFavorite);
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@@ -711,6 +803,7 @@ class _$PlanModelImpl implements _PlanModel {
abstract class _PlanModel implements PlanModel {
const factory _PlanModel(
{@JsonKey(name: "id") required final int id,
@JsonKey(name: "slug") required final String slug,
@JsonKey(name: "name") required final String name,
@JsonKey(name: "image") required final String image,
@JsonKey(name: "duration") required final int duration,
@@ -718,13 +811,14 @@ abstract class _PlanModel implements PlanModel {
@JsonKey(name: "type") required final PlanType type,
@JsonKey(name: "details") required final String details,
@JsonKey(name: "packets_count") required final int packetsCount,
@JsonKey(name: "packet1_options") final dynamic packet1Options,
@JsonKey(name: "packet2_options") final dynamic packet2Options,
@JsonKey(name: "packet3_options") final dynamic packet3Options,
@JsonKey(name: "packet1_options") final PacketOptions? packet1Options,
@JsonKey(name: "packet2_options") final PacketOptions? packet2Options,
@JsonKey(name: "packet3_options") final PacketOptions? packet3Options,
@JsonKey(name: "author") required final Author author,
@JsonKey(name: "total_likes") required final int totalLikes,
@JsonKey(name: "total_views") required final int totalViews,
@JsonKey(name: "total_comments") required final int totalComments}) =
@JsonKey(name: "total_comments") required final int totalComments,
@JsonKey(name: "is_favorite") required final bool isFavorite}) =
_$PlanModelImpl;
factory _PlanModel.fromJson(Map<String, dynamic> json) =
@@ -734,6 +828,9 @@ abstract class _PlanModel implements PlanModel {
@JsonKey(name: "id")
int get id;
@override
@JsonKey(name: "slug")
String get slug;
@override
@JsonKey(name: "name")
String get name;
@override
@@ -756,13 +853,13 @@ abstract class _PlanModel implements PlanModel {
int get packetsCount;
@override
@JsonKey(name: "packet1_options")
dynamic get packet1Options;
PacketOptions? get packet1Options;
@override
@JsonKey(name: "packet2_options")
dynamic get packet2Options;
PacketOptions? get packet2Options;
@override
@JsonKey(name: "packet3_options")
dynamic get packet3Options;
PacketOptions? get packet3Options;
@override
@JsonKey(name: "author")
Author get author;
@@ -775,6 +872,9 @@ abstract class _PlanModel implements PlanModel {
@override
@JsonKey(name: "total_comments")
int get totalComments;
@override
@JsonKey(name: "is_favorite")
bool get isFavorite;
/// Create a copy of PlanModel
/// with the given fields replaced by the non-null parameter values.
@@ -1030,13 +1130,13 @@ PlanType _$PlanTypeFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$PlanType {
@JsonKey(name: "fit")
bool get fit => throw _privateConstructorUsedError;
bool? get fit => throw _privateConstructorUsedError;
@JsonKey(name: "slim")
bool get slim => throw _privateConstructorUsedError;
bool? get slim => throw _privateConstructorUsedError;
@JsonKey(name: "behavior")
bool get behavior => throw _privateConstructorUsedError;
bool? get behavior => throw _privateConstructorUsedError;
@JsonKey(name: "wellness")
bool get wellness => throw _privateConstructorUsedError;
bool? get wellness => throw _privateConstructorUsedError;
/// Serializes this PlanType to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -1054,10 +1154,10 @@ abstract class $PlanTypeCopyWith<$Res> {
_$PlanTypeCopyWithImpl<$Res, PlanType>;
@useResult
$Res call(
{@JsonKey(name: "fit") bool fit,
@JsonKey(name: "slim") bool slim,
@JsonKey(name: "behavior") bool behavior,
@JsonKey(name: "wellness") bool wellness});
{@JsonKey(name: "fit") bool? fit,
@JsonKey(name: "slim") bool? slim,
@JsonKey(name: "behavior") bool? behavior,
@JsonKey(name: "wellness") bool? wellness});
}
/// @nodoc
@@ -1075,28 +1175,28 @@ class _$PlanTypeCopyWithImpl<$Res, $Val extends PlanType>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? fit = null,
Object? slim = null,
Object? behavior = null,
Object? wellness = null,
Object? fit = freezed,
Object? slim = freezed,
Object? behavior = freezed,
Object? wellness = freezed,
}) {
return _then(_value.copyWith(
fit: null == fit
fit: freezed == fit
? _value.fit
: fit // ignore: cast_nullable_to_non_nullable
as bool,
slim: null == slim
as bool?,
slim: freezed == slim
? _value.slim
: slim // ignore: cast_nullable_to_non_nullable
as bool,
behavior: null == behavior
as bool?,
behavior: freezed == behavior
? _value.behavior
: behavior // ignore: cast_nullable_to_non_nullable
as bool,
wellness: null == wellness
as bool?,
wellness: freezed == wellness
? _value.wellness
: wellness // ignore: cast_nullable_to_non_nullable
as bool,
as bool?,
) as $Val);
}
}
@@ -1110,10 +1210,10 @@ abstract class _$$PlanTypeImplCopyWith<$Res>
@override
@useResult
$Res call(
{@JsonKey(name: "fit") bool fit,
@JsonKey(name: "slim") bool slim,
@JsonKey(name: "behavior") bool behavior,
@JsonKey(name: "wellness") bool wellness});
{@JsonKey(name: "fit") bool? fit,
@JsonKey(name: "slim") bool? slim,
@JsonKey(name: "behavior") bool? behavior,
@JsonKey(name: "wellness") bool? wellness});
}
/// @nodoc
@@ -1129,56 +1229,57 @@ class __$$PlanTypeImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? fit = null,
Object? slim = null,
Object? behavior = null,
Object? wellness = null,
Object? fit = freezed,
Object? slim = freezed,
Object? behavior = freezed,
Object? wellness = freezed,
}) {
return _then(_$PlanTypeImpl(
fit: null == fit
fit: freezed == fit
? _value.fit
: fit // ignore: cast_nullable_to_non_nullable
as bool,
slim: null == slim
as bool?,
slim: freezed == slim
? _value.slim
: slim // ignore: cast_nullable_to_non_nullable
as bool,
behavior: null == behavior
as bool?,
behavior: freezed == behavior
? _value.behavior
: behavior // ignore: cast_nullable_to_non_nullable
as bool,
wellness: null == wellness
as bool?,
wellness: freezed == wellness
? _value.wellness
: wellness // ignore: cast_nullable_to_non_nullable
as bool,
as bool?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PlanTypeImpl implements _PlanType {
class _$PlanTypeImpl extends _PlanType {
const _$PlanTypeImpl(
{@JsonKey(name: "fit") required this.fit,
@JsonKey(name: "slim") required this.slim,
@JsonKey(name: "behavior") required this.behavior,
@JsonKey(name: "wellness") required this.wellness});
@JsonKey(name: "wellness") required this.wellness})
: super._();
factory _$PlanTypeImpl.fromJson(Map<String, dynamic> json) =>
_$$PlanTypeImplFromJson(json);
@override
@JsonKey(name: "fit")
final bool fit;
final bool? fit;
@override
@JsonKey(name: "slim")
final bool slim;
final bool? slim;
@override
@JsonKey(name: "behavior")
final bool behavior;
final bool? behavior;
@override
@JsonKey(name: "wellness")
final bool wellness;
final bool? wellness;
@override
String toString() {
@@ -1218,29 +1319,30 @@ class _$PlanTypeImpl implements _PlanType {
}
}
abstract class _PlanType implements PlanType {
abstract class _PlanType extends PlanType {
const factory _PlanType(
{@JsonKey(name: "fit") required final bool fit,
@JsonKey(name: "slim") required final bool slim,
@JsonKey(name: "behavior") required final bool behavior,
@JsonKey(name: "wellness") required final bool wellness}) =
{@JsonKey(name: "fit") required final bool? fit,
@JsonKey(name: "slim") required final bool? slim,
@JsonKey(name: "behavior") required final bool? behavior,
@JsonKey(name: "wellness") required final bool? wellness}) =
_$PlanTypeImpl;
const _PlanType._() : super._();
factory _PlanType.fromJson(Map<String, dynamic> json) =
_$PlanTypeImpl.fromJson;
@override
@JsonKey(name: "fit")
bool get fit;
bool? get fit;
@override
@JsonKey(name: "slim")
bool get slim;
bool? get slim;
@override
@JsonKey(name: "behavior")
bool get behavior;
bool? get behavior;
@override
@JsonKey(name: "wellness")
bool get wellness;
bool? get wellness;
/// Create a copy of PlanType
/// with the given fields replaced by the non-null parameter values.

View File

@@ -27,6 +27,7 @@ Map<String, dynamic> _$$PlanPaginationModelImplToJson(
_$PlanModelImpl _$$PlanModelImplFromJson(Map<String, dynamic> json) =>
_$PlanModelImpl(
id: (json['id'] as num).toInt(),
slug: json['slug'] as String,
name: json['name'] as String,
image: json['image'] as String,
duration: (json['duration'] as num).toInt(),
@@ -34,18 +35,29 @@ _$PlanModelImpl _$$PlanModelImplFromJson(Map<String, dynamic> json) =>
type: PlanType.fromJson(json['type'] as Map<String, dynamic>),
details: json['details'] as String,
packetsCount: (json['packets_count'] as num).toInt(),
packet1Options: json['packet1_options'],
packet2Options: json['packet2_options'],
packet3Options: json['packet3_options'],
packet1Options: json['packet1_options'] == null
? null
: PacketOptions.fromJson(
json['packet1_options'] as Map<String, dynamic>),
packet2Options: json['packet2_options'] == null
? null
: PacketOptions.fromJson(
json['packet2_options'] as Map<String, dynamic>),
packet3Options: json['packet3_options'] == null
? null
: PacketOptions.fromJson(
json['packet3_options'] 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(),
totalComments: (json['total_comments'] as num).toInt(),
isFavorite: json['is_favorite'] as bool,
);
Map<String, dynamic> _$$PlanModelImplToJson(_$PlanModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'slug': instance.slug,
'name': instance.name,
'image': instance.image,
'duration': instance.duration,
@@ -60,6 +72,7 @@ Map<String, dynamic> _$$PlanModelImplToJson(_$PlanModelImpl instance) =>
'total_likes': instance.totalLikes,
'total_views': instance.totalViews,
'total_comments': instance.totalComments,
'is_favorite': instance.isFavorite,
};
_$PacketOptionsImpl _$$PacketOptionsImplFromJson(Map<String, dynamic> json) =>
@@ -80,10 +93,10 @@ Map<String, dynamic> _$$PacketOptionsImplToJson(_$PacketOptionsImpl instance) =>
_$PlanTypeImpl _$$PlanTypeImplFromJson(Map<String, dynamic> json) =>
_$PlanTypeImpl(
fit: json['fit'] as bool,
slim: json['slim'] as bool,
behavior: json['behavior'] as bool,
wellness: json['wellness'] as bool,
fit: json['fit'] as bool?,
slim: json['slim'] as bool?,
behavior: json['behavior'] as bool?,
wellness: json['wellness'] as bool?,
);
Map<String, dynamic> _$$PlanTypeImplToJson(_$PlanTypeImpl instance) =>

View File

@@ -27,6 +27,9 @@ class CommentsCubit extends Cubit<CommentsState> {
case CommentSourceType.recipe:
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id: id)));
break;
case CommentSourceType.plan:
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
break;
}
} catch (e) {
emit(CommentsState.error(errorMessage: e.toString()));
@@ -62,6 +65,13 @@ class CommentsCubit extends Cubit<CommentsState> {
parentCommentId: parentCommentId,
);
break;
case CommentSourceType.plan:
await feedRepository.sendPlansComment(
slug: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break;
}
getComments(id, commentSourceType);
} catch (e) {

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
@@ -30,7 +32,9 @@ class PlansCubit extends Cubit<PlansState> {
isLoading: false,
),
);
} catch (e) {
} catch (e, stackTrace) {
log(e.toString());
log(stackTrace.toString());
emit(state.copyWith(errorMessage: e.toString()));
}
}

View File

@@ -0,0 +1,19 @@
import 'dart:developer';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FeedProfileCubit extends Cubit<ProfileState> {
final ProfileRepository profileRepository;
FeedProfileCubit({required this.profileRepository}) : super(ProfileState(profile: null));
Future<void> getProfile() async {
try {
final profile = await profileRepository.getMyProfile();
emit(state.copyWith(profile: profile));
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
}
}
}

View File

@@ -0,0 +1,13 @@
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'profile_state.freezed.dart';
@freezed
class ProfileState with _$ProfileState {
factory ProfileState({
ProfileModel? profile,
}) = _ProfileState;
}

View File

@@ -0,0 +1,162 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'profile_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ProfileState {
ProfileModel? get profile => throw _privateConstructorUsedError;
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProfileStateCopyWith<ProfileState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProfileStateCopyWith<$Res> {
factory $ProfileStateCopyWith(
ProfileState value, $Res Function(ProfileState) then) =
_$ProfileStateCopyWithImpl<$Res, ProfileState>;
@useResult
$Res call({ProfileModel? profile});
$ProfileModelCopyWith<$Res>? get profile;
}
/// @nodoc
class _$ProfileStateCopyWithImpl<$Res, $Val extends ProfileState>
implements $ProfileStateCopyWith<$Res> {
_$ProfileStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? profile = freezed,
}) {
return _then(_value.copyWith(
profile: freezed == profile
? _value.profile
: profile // ignore: cast_nullable_to_non_nullable
as ProfileModel?,
) as $Val);
}
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProfileModelCopyWith<$Res>? get profile {
if (_value.profile == null) {
return null;
}
return $ProfileModelCopyWith<$Res>(_value.profile!, (value) {
return _then(_value.copyWith(profile: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ProfileStateImplCopyWith<$Res>
implements $ProfileStateCopyWith<$Res> {
factory _$$ProfileStateImplCopyWith(
_$ProfileStateImpl value, $Res Function(_$ProfileStateImpl) then) =
__$$ProfileStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({ProfileModel? profile});
@override
$ProfileModelCopyWith<$Res>? get profile;
}
/// @nodoc
class __$$ProfileStateImplCopyWithImpl<$Res>
extends _$ProfileStateCopyWithImpl<$Res, _$ProfileStateImpl>
implements _$$ProfileStateImplCopyWith<$Res> {
__$$ProfileStateImplCopyWithImpl(
_$ProfileStateImpl _value, $Res Function(_$ProfileStateImpl) _then)
: super(_value, _then);
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? profile = freezed,
}) {
return _then(_$ProfileStateImpl(
profile: freezed == profile
? _value.profile
: profile // ignore: cast_nullable_to_non_nullable
as ProfileModel?,
));
}
}
/// @nodoc
class _$ProfileStateImpl implements _ProfileState {
_$ProfileStateImpl({this.profile});
@override
final ProfileModel? profile;
@override
String toString() {
return 'ProfileState(profile: $profile)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProfileStateImpl &&
(identical(other.profile, profile) || other.profile == profile));
}
@override
int get hashCode => Object.hash(runtimeType, profile);
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProfileStateImplCopyWith<_$ProfileStateImpl> get copyWith =>
__$$ProfileStateImplCopyWithImpl<_$ProfileStateImpl>(this, _$identity);
}
abstract class _ProfileState implements ProfileState {
factory _ProfileState({final ProfileModel? profile}) = _$ProfileStateImpl;
@override
ProfileModel? get profile;
/// Create a copy of ProfileState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProfileStateImplCopyWith<_$ProfileStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -6,6 +6,8 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cub
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
@@ -92,79 +94,94 @@ class _FeedScreenState extends State<FeedScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _selectedChipIndex == 1
? FloatingActionButton.small(
heroTag: 'recipesTagsFilter',
backgroundColor: appTheme.blueA400,
shape: CircleBorder(),
child: Icon(
Icons.filter_list_rounded,
color: Colors.white,
),
onPressed: () async {
await showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => TagsModalWidget(
recipesCubit: recipesCubit,
),
);
recipesCubit.loadRecipesList();
},
)
: null,
appBar: AppBar(
backgroundColor: Colors.white,
toolbarHeight: 0,
),
backgroundColor: Colors.grey.shade200,
body: NestedScrollView(
controller: switch (_selectedChipIndex) {
0 => blogPostsScrollController,
1 => recipesScrollController,
2 => dietsScrollController,
3 => plansScrollController,
_ => blogPostsScrollController
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
),
backgroundColor: Colors.white,
foregroundColor: Colors.white,
automaticallyImplyLeading: false,
stretchTriggerOffset: 1,
floating: true,
snap: true,
scrolledUnderElevation: 0,
bottom: _chipGroup(),
title: Row(
children: [
_searchBar(),
SizedBox(width: 12),
_addItemButton(feedCubit),
],
),
),
];
},
body: Builder(
builder: (BuildContext context) {
if (_selectedChipIndex == 0) {
return _buildAllBlogPosts();
} else if (_selectedChipIndex == 1) {
return _buildAllRecipes();
} else if (_selectedChipIndex == 2) {
return _buildDiets();
} else if (_selectedChipIndex == 3) {
return _buildPlans();
} else {
return Center(child: Text(chipsLabelList[_selectedChipIndex]));
}
return BlocProvider(
create: (context) => sl<FeedProfileCubit>()..getProfile(),
child: Scaffold(
floatingActionButton: _selectedChipIndex == 1
? FloatingActionButton.small(
heroTag: 'recipesTagsFilter',
backgroundColor: appTheme.blueA400,
shape: CircleBorder(),
child: Icon(
Icons.filter_list_rounded,
color: Colors.white,
),
onPressed: () async {
await showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => TagsModalWidget(
recipesCubit: recipesCubit,
),
);
recipesCubit.loadRecipesList();
},
)
: null,
appBar: AppBar(
backgroundColor: Colors.white,
toolbarHeight: 0,
),
backgroundColor: Colors.grey.shade200,
body: NestedScrollView(
controller: switch (_selectedChipIndex) {
0 => blogPostsScrollController,
1 => recipesScrollController,
2 => dietsScrollController,
3 => plansScrollController,
_ => blogPostsScrollController
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
BlocBuilder<FeedProfileCubit, ProfileState>(
builder: (context, state) {
return SliverAppBar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
),
backgroundColor: Colors.white,
foregroundColor: Colors.white,
automaticallyImplyLeading: false,
stretchTriggerOffset: 1,
floating: true,
snap: true,
scrolledUnderElevation: 0,
bottom: _chipGroup(),
title: Row(
children: [
_searchBar(),
SizedBox(width: 12),
switch (_selectedChipIndex) {
2 => _addItemButton(
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null,
),
3 => _addItemButton(
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => _addItemButton()
},
],
),
);
},
),
];
},
body: Builder(
builder: (BuildContext context) {
if (_selectedChipIndex == 0) {
return _buildAllBlogPosts();
} else if (_selectedChipIndex == 1) {
return _buildAllRecipes();
} else if (_selectedChipIndex == 2) {
return _buildDiets();
} else if (_selectedChipIndex == 3) {
return _buildPlans();
} else {
return Center(child: Text(chipsLabelList[_selectedChipIndex]));
}
},
),
),
),
);
@@ -435,20 +452,12 @@ class _FeedScreenState extends State<FeedScreen> {
);
}
_addItemButton(FeedCubit feedCubit) {
_addItemButton({void Function()? onTap}) {
return InkWell(
onTap: () {
switch (_selectedChipIndex) {
case 2:
GoRouter.of(context).pushNamed(CreateDietScreen.name);
break;
case 3:
GoRouter.of(context).pushNamed(CreatePlanScreen.name);
break;
default:
onTap: onTap ??
() {
GoRouter.of(context).pushNamed('blogPostCreateScreen');
}
},
},
borderRadius: BorderRadius.circular(64),
child: Ink(
decoration: BoxDecoration(

View File

@@ -88,7 +88,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
],
),
const Spacer(),
if (!state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe && !state.feedBlogPostList[widget.index].author.authorIsMe)
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) && !state.feedBlogPostList[widget.index].author.authorIsMe)
SubscribeButtonWidget(
slug: state.feedBlogPostList[widget.index].author.authorSlug,
),

View File

@@ -6,7 +6,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum CommentSourceType { blogPost, recipe, diet }
enum CommentSourceType { blogPost, recipe, diet, plan }
class CommentsWidget extends StatefulWidget {
final String id;

View File

@@ -75,7 +75,7 @@ class _DietsWidgetState extends State<DietsWidget> {
bottom: 16,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: DietInfoRow(
child: InfoRow(
diet: widget.diet,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,

View File

@@ -19,7 +19,10 @@ class PlanWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'aboutPlanScreen',
extra: planModel,
extra: {
'plan': planModel,
'navigateToComments': false,
},
),
child: Ink(
decoration: BoxDecoration(

View File

@@ -157,6 +157,32 @@ class FeedRepository {
}
}
Future<CommentModel> getPlansComments({
required String slug,
}) async {
try {
return (await feedService.getPlansComments(slug: slug));
} catch (e) {
rethrow;
}
}
Future<void> sendPlansComment({
required String slug,
required String commentText,
int? parentCommentId,
}) async {
try {
await feedService.sendPlansComment(
slug: slug,
body: commentText,
parentCommentId: parentCommentId,
);
} catch (e) {
rethrow;
}
}
Future<void> likeBlogPost({
required int id,
}) async {

View File

@@ -63,6 +63,18 @@ abstract class FeedService {
@Part() int? parentCommentId,
});
@GET('/api/plans/{slug}/comments/show')
Future<CommentModel> getPlansComments({
@Path() required String slug,
});
@POST('/api/plans/{slug}/comment/create')
Future<void> sendPlansComment({
@Path() required String slug,
@Part() required String body,
@Part() int? parentCommentId,
});
@GET('/api/recipes/{id}/comments/show')
Future<CommentModel> getRecipesComments({
@Path() required String id,

View File

@@ -328,6 +328,79 @@ class _FeedService implements FeedService {
await _dio.fetch<void>(_options);
}
@override
Future<CommentModel> getPlansComments({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<CommentModel>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/${slug}/comments/show',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late CommentModel _value;
try {
_value = CommentModel.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
@override
Future<void> sendPlansComment({
required String slug,
required String body,
int? parentCommentId,
}) 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(
'body',
body,
));
if (parentCommentId != null) {
_data.fields.add(MapEntry(
'parentCommentId',
parentCommentId.toString(),
));
}
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/${slug}/comment/create',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
@override
Future<CommentModel> getRecipesComments({required String id}) async {
final _extra = <String, dynamic>{};

View File

@@ -1,11 +1,72 @@
import 'dart:developer';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AboutPlanCubit extends Cubit<AboutPlanState> {
AboutPlanCubit() : super(AboutPlanState.initial());
final PlanRepository planRepository;
AboutPlanCubit({required this.planRepository}) : super(AboutPlanState.initial());
Future<void> getPlan(String slug) async {
try {
final plan = await planRepository.getPlan(slug);
emit(state.copyWith(planModel: plan, error: null));
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
emit(state.copyWith(error: e.toString()));
}
}
Future<void> getPlan() async{
Future<void> getPlanWithLoading(String slug) async {
try {
emit(state.copyWith(isLoading: true));
final plan = await planRepository.getPlan(slug);
emit(state.copyWith(
planModel: plan,
error: null,
isLoading: false,
),);
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
emit(state.copyWith(error: e.toString(), isLoading: false));
}
}
void choseOption(int index) {
emit(state.copyWith(chosenOptionIndex: index));
}
Future<void> likePlan(String slug) async {
try {
await planRepository.likePlan(slug);
final plan = await planRepository.getPlan(slug);
emit(state.copyWith(planModel: plan, error: null));
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
emit(state.copyWith(error: e.toString()));
}
}
Future<void> addToFavorite(String slug) async {
try {
await planRepository.addToFavorite(slug);
final plan = await planRepository.getPlan(slug);
emit(state.copyWith(planModel: plan, error: null));
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
emit(state.copyWith(error: e.toString()));
}
}
Future<void> removeFromFavorite(String slug) async {
try {
await planRepository.removeFromFavorite(slug);
final plan = await planRepository.getPlan(slug);
emit(state.copyWith(planModel: plan, error: null));
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
emit(state.copyWith(error: e.toString()));
}
}
}

View File

@@ -12,7 +12,8 @@ class AboutPlanState with _$AboutPlanState {
required bool isLoading,
required String? error,
required PlanModel? planModel,
required int chosenOptionIndex,
}) = _AboutPlanState;
factory AboutPlanState.initial() => const AboutPlanState(isLoading: false, error: null, planModel: null);
factory AboutPlanState.initial() => const AboutPlanState(chosenOptionIndex: 0, isLoading: false, error: null, planModel: null);
}

View File

@@ -19,6 +19,7 @@ mixin _$AboutPlanState {
bool get isLoading => throw _privateConstructorUsedError;
String? get error => throw _privateConstructorUsedError;
PlanModel? get planModel => throw _privateConstructorUsedError;
int get chosenOptionIndex => throw _privateConstructorUsedError;
/// Create a copy of AboutPlanState
/// with the given fields replaced by the non-null parameter values.
@@ -33,7 +34,11 @@ abstract class $AboutPlanStateCopyWith<$Res> {
AboutPlanState value, $Res Function(AboutPlanState) then) =
_$AboutPlanStateCopyWithImpl<$Res, AboutPlanState>;
@useResult
$Res call({bool isLoading, String? error, PlanModel? planModel});
$Res call(
{bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex});
$PlanModelCopyWith<$Res>? get planModel;
}
@@ -56,6 +61,7 @@ class _$AboutPlanStateCopyWithImpl<$Res, $Val extends AboutPlanState>
Object? isLoading = null,
Object? error = freezed,
Object? planModel = freezed,
Object? chosenOptionIndex = null,
}) {
return _then(_value.copyWith(
isLoading: null == isLoading
@@ -70,6 +76,10 @@ class _$AboutPlanStateCopyWithImpl<$Res, $Val extends AboutPlanState>
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex: null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
@@ -96,7 +106,11 @@ abstract class _$$AboutPlanStateImplCopyWith<$Res>
__$$AboutPlanStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isLoading, String? error, PlanModel? planModel});
$Res call(
{bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex});
@override
$PlanModelCopyWith<$Res>? get planModel;
@@ -118,6 +132,7 @@ class __$$AboutPlanStateImplCopyWithImpl<$Res>
Object? isLoading = null,
Object? error = freezed,
Object? planModel = freezed,
Object? chosenOptionIndex = null,
}) {
return _then(_$AboutPlanStateImpl(
isLoading: null == isLoading
@@ -132,6 +147,10 @@ class __$$AboutPlanStateImplCopyWithImpl<$Res>
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex: null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
@@ -140,7 +159,10 @@ class __$$AboutPlanStateImplCopyWithImpl<$Res>
class _$AboutPlanStateImpl implements _AboutPlanState {
const _$AboutPlanStateImpl(
{required this.isLoading, required this.error, required this.planModel});
{required this.isLoading,
required this.error,
required this.planModel,
required this.chosenOptionIndex});
@override
final bool isLoading;
@@ -148,10 +170,12 @@ class _$AboutPlanStateImpl implements _AboutPlanState {
final String? error;
@override
final PlanModel? planModel;
@override
final int chosenOptionIndex;
@override
String toString() {
return 'AboutPlanState(isLoading: $isLoading, error: $error, planModel: $planModel)';
return 'AboutPlanState(isLoading: $isLoading, error: $error, planModel: $planModel, chosenOptionIndex: $chosenOptionIndex)';
}
@override
@@ -163,11 +187,14 @@ class _$AboutPlanStateImpl implements _AboutPlanState {
other.isLoading == isLoading) &&
(identical(other.error, error) || other.error == error) &&
(identical(other.planModel, planModel) ||
other.planModel == planModel));
other.planModel == planModel) &&
(identical(other.chosenOptionIndex, chosenOptionIndex) ||
other.chosenOptionIndex == chosenOptionIndex));
}
@override
int get hashCode => Object.hash(runtimeType, isLoading, error, planModel);
int get hashCode =>
Object.hash(runtimeType, isLoading, error, planModel, chosenOptionIndex);
/// Create a copy of AboutPlanState
/// with the given fields replaced by the non-null parameter values.
@@ -183,7 +210,8 @@ abstract class _AboutPlanState implements AboutPlanState {
const factory _AboutPlanState(
{required final bool isLoading,
required final String? error,
required final PlanModel? planModel}) = _$AboutPlanStateImpl;
required final PlanModel? planModel,
required final int chosenOptionIndex}) = _$AboutPlanStateImpl;
@override
bool get isLoading;
@@ -191,6 +219,8 @@ abstract class _AboutPlanState implements AboutPlanState {
String? get error;
@override
PlanModel? get planModel;
@override
int get chosenOptionIndex;
/// Create a copy of AboutPlanState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -1,19 +1,480 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart';
import 'package:eatvio_app/generated/l10n.dart';
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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
class AboutPlanScreen extends StatelessWidget {
final PlanModel planModel;
import '../widgets/option_widget.dart';
const AboutPlanScreen({
super.key,
required this.planModel,
});
class AboutPlanScreen extends StatefulWidget {
AboutPlanScreen({super.key, required this.plan, required this.navigateToComments});
final PlanModel plan;
final bool navigateToComments;
@override
State<AboutPlanScreen> createState() => _AboutPlanScreenState();
}
class _AboutPlanScreenState extends State<AboutPlanScreen> {
final CommentsCubit commentsCubit = sl<CommentsCubit>();
int? replyId;
final ScrollController scrollController = ScrollController();
final textEditingController = TextEditingController();
bool isMoreOptionsVisible = false;
bool _jumped = false;
@override
void initState() {
super.initState();
scrollController.addListener(() {
if (isMoreOptionsVisible) {
setState(() {
isMoreOptionsVisible = false;
});
}
});
}
void _scrollToBottom() {
_jumped = true;
scrollController.jumpTo(
scrollController.position.maxScrollExtent,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(planModel.name),
final s = S.of(context);
return MultiBlocProvider(
providers: [
BlocProvider<CommentsCubit>(
create: (context) => commentsCubit
..getComments(
widget.plan.slug,
CommentSourceType.plan,
true,
),
),
BlocProvider<AboutPlanCubit>(
create: (context) => sl<AboutPlanCubit>()..getPlanWithLoading(widget.plan.slug),
),
],
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
setState(() {
replyId = commentsState.replyId;
});
}
},
builder: (BuildContext context, CommentsState commentsState) {
Future.delayed(Duration(milliseconds: 500), () {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
}
});
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
if (state.isLoading) return Scaffold(body: Loader());
return Scaffold(
backgroundColor: appTheme.gray100,
resizeToAvoidBottomInset: true,
bottomSheet: Container(
padding: EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: Colors.black.withOpacity(0.1),
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: SizedBox(
child: FloatingActionButton.extended(
heroTag: 'nextButtonRegistration',
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
backgroundColor: appTheme.greenA700,
onPressed: () {
/// TODO: добавить обработку покупки плана
// showModalBottomSheet(
// context: context,
// builder: (context) => StartDietBottomSheet(
// aboutDietCubit: context.read<AboutDietCubit>(),
// ),
// );
},
label: Text(
s.startDiet,
style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
),
),
),
),
SizedBox(width: 8),
BookmarkButton(
isFavorite: state.planModel?.isFavorite ?? false,
onTap: () {
final planModel = state.planModel;
if (planModel == null) return;
if (planModel.isFavorite) {
context.read<AboutPlanCubit>().removeFromFavorite(planModel.slug);
} else {
context.read<AboutPlanCubit>().addToFavorite(planModel.slug);
}
},
width: 52,
height: 52,
size: 24,
shape: BoxShape.circle,
color: appTheme.gray50,
),
],
),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) SizedBox(width: 8),
Expanded(
child: SizedBox(
child: TextFormField(
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
hintText: s.comment.capitalize(),
hintStyle: TextStyles.textMMedium.copyWith(color: appTheme.lightGray600),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: Color(0xFFD1D1D6),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: Color(0xFFD1D1D6),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: Color(0xFFD1D1D6),
width: 1,
),
),
),
),
),
),
SizedBox(width: 8),
GestureDetector(
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.plan.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.plan,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
Future.delayed(Duration(milliseconds: 500), () {
context.read<AboutPlanCubit>().getPlan(widget.plan.slug);
});
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: Container(
decoration: BoxDecoration(
color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.send_rounded,
size: 18,
color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
),
),
),
],
),
],
),
),
),
appBar: AppBar(
centerTitle: true,
title: Text(
widget.plan.name,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(id: widget.plan.id),
anchor: Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
),
],
),
body: SingleChildScrollView(
controller: scrollController,
padding: EdgeInsets.only(bottom: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 8),
MainBackgroundBouble(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.plan.image,
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
),
Positioned(
bottom: 16,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoBouble(
children: [
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
SizedBox(width: 8),
Text(
widget.plan.totalViews.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
SizedBox(width: 12),
GestureDetector(
onTap: () {
context.read<AboutPlanCubit>().likePlan(widget.plan.slug);
},
child: InfoBouble(
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
width: 14,
height: 12,
/// TODO задача для бека на добавление wasLokedByMe?
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
),
SizedBox(width: 8),
Text(
widget.plan.totalLikes.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
),
SizedBox(width: 12),
InfoBouble(
children: [
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
SizedBox(width: 8),
Text(
widget.plan.totalComments.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
],
),
),
],
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
s.about_plan,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
SizedBox(height: 12),
/// TODO глянуть как заменить
InfoRow(plan: state.planModel),
ExpandableText(
text: widget.plan.details,
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.plan.author,
title: s.plan_author,
),
),
SizedBox(height: 16),
],
),
),
SizedBox(height: 16),
if (state.planModel?.packetsCount == 3) ...[
OptionsTab(
cost: [
state.planModel?.packet1Options?.price ?? 0,
state.planModel?.packet2Options?.price ?? 0,
state.planModel?.packet3Options?.price ?? 0,
],
),
SizedBox(height: 12),
],
switch (state.chosenOptionIndex) {
1 => OptionWidget(
option: state.planModel?.packet2Options,
),
2 => OptionWidget(
option: state.planModel?.packet3Options,
),
_ => OptionWidget(
option: state.planModel?.packet1Options,
),
},
SizedBox(
height: 16,
),
MainBackgroundBouble(
padding: EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}',
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray500,
),
),
),
SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
if (commentsState is CommentsStateLoaded)
CommentsWidget(
id: widget.plan.id.toString(),
commentSourceType: CommentSourceType.plan,
comment: commentsState.comment,
replyId: replyId,
),
],
),
),
],
),
),
);
},
);
},
),
);
}

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/flutter_svg.dart';
class ItemWidget extends StatelessWidget {
const ItemWidget({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(
text,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
Spacer(),
SvgPicture.asset(
'assets/icons/ok.svg',
width: 20,
height: 20,
),
],
);
}
}

View File

@@ -0,0 +1,45 @@
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/generated/l10n.dart';
import 'package:flutter/material.dart';
class OptionDurationWidget extends StatelessWidget {
const OptionDurationWidget({super.key, required this.duration});
final int duration;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), color: appTheme.cardBackground),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Container(
width: 4,
height: 20,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.only(
topRight: Radius.circular(100),
bottomRight: Radius.circular(100),
),
),
),
),
SizedBox(
width: 12,
),
Text(
'${s.option_duration} $duration ${getDayForm(duration, s)}',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(
width: 12,
),
],
),
);
}
}

View File

@@ -0,0 +1,49 @@
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_divider.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/presentation/widgets/item_widget.dart';
import 'package:eatvio_app/features/plans/presentation/widgets/option_duration_widget.dart';
import 'package:flutter/material.dart';
class OptionWidget extends StatelessWidget {
const OptionWidget({super.key, required this.option});
final PacketOptions? option;
@override
Widget build(BuildContext context) {
if (option == null) {
return SizedBox.shrink();
}
return MainBackgroundBouble(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16),
Text(
option?.short ?? '',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
SizedBox(height: 12),
Text(
option?.description ?? '',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 12),
OptionDurationWidget(duration: 7),
SizedBox(height: 16),
CustomDivider(),
SizedBox(height: 16),
ListView.separated(
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 12),
itemBuilder: (context, index) => ItemWidget(text: option?.items[index] ?? ''),
itemCount: option?.items.length ?? 0,
shrinkWrap: true,
),
],
),
);
}
}

View File

@@ -0,0 +1,43 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class OptionsTab extends StatelessWidget {
const OptionsTab({super.key, required this.cost});
final List<num> cost;
@override
Widget build(BuildContext context) {
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
final selectedOption = state.chosenOptionIndex;
return Container(
margin: EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(100),
),
padding: const EdgeInsets.all(4),
child: Row(
children: [
...List.generate(
3,
(index) => Expanded(
child: AnimatedOptionButton(
isSelected: selectedOption == index,
text: '${cost[index]}₽',
onTap: () {
context.read<AboutPlanCubit>().choseOption(index);
},
),
),
),
],
),
);
},
);
}
}

View File

@@ -0,0 +1,43 @@
import 'dart:developer';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/services/plan_service.dart';
class PlanRepository {
final PlanService planService;
PlanRepository({required this.planService});
Future<PlanModel> getPlan(String slug) async {
try {
return planService.getPlan(slug);
} catch (e) {
rethrow;
}
}
Future<void> likePlan(String slug) async {
try {
await planService.likePlan(slug: slug);
} catch (e) {
rethrow;
}
}
Future<void> addToFavorite(String slug) async {
try {
await planService.addToFavorite(slug: slug);
} catch (e) {
rethrow;
}
}
Future<void> removeFromFavorite(String slug) async {
try {
await planService.removeFromFavorite(slug: slug);
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
rethrow;
}
}
}

View File

@@ -0,0 +1,31 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:retrofit/retrofit.dart';
part 'plan_service.g.dart';
@RestApi()
abstract class PlanService {
factory PlanService(Dio dio, {String baseUrl}) = _PlanService;
@GET('/api/plans/show/{slug}')
Future<PlanModel> getPlan(@Path('slug') String slug);
@POST('/api/plans/{slug}/like')
Future<void> likePlan({
@Path() required String slug,
});
@POST('/api/plans/{slug}/add_to_favorite')
Future<void> addToFavorite({
@Path() required String slug,
});
@POST('/api/plans/{slug}/remove_from_favorite')
Future<void> removeFromFavorite({
@Path() required String slug,
});
}

View File

@@ -0,0 +1,161 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'plan_service.dart';
// **************************************************************************
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
class _PlanService implements PlanService {
_PlanService(
this._dio, {
this.baseUrl,
this.errorLogger,
});
final Dio _dio;
String? baseUrl;
final ParseErrorLogger? errorLogger;
@override
Future<PlanModel> getPlan(String slug) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<PlanModel>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/show/${slug}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late PlanModel _value;
try {
_value = PlanModel.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
@override
Future<void> likePlan({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/${slug}/like',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
@override
Future<void> addToFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/${slug}/add_to_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
@override
Future<void> removeFromFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/plans/${slug}/remove_from_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
return requestOptions;
}
String _combineBaseUrls(
String dioBaseUrl,
String? baseUrl,
) {
if (baseUrl == null || baseUrl.trim().isEmpty) {
return dioBaseUrl;
}
final url = Uri.parse(baseUrl);
if (url.isAbsolute) {
return url.toString();
}
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
}
}