forked from eatvio/eatvio_app
diet creation scrren is done
This commit is contained in:
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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 ?? ''),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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 ?? '')],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 ?? ''),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>{};
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
26
lib/features/plans/presentation/widgets/item_widget.dart
Normal file
26
lib/features/plans/presentation/widgets/item_widget.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
49
lib/features/plans/presentation/widgets/option_widget.dart
Normal file
49
lib/features/plans/presentation/widgets/option_widget.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/features/plans/presentation/widgets/options_tab.dart
Normal file
43
lib/features/plans/presentation/widgets/options_tab.dart
Normal 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);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/features/plans/repositories/plan_repository.dart
Normal file
43
lib/features/plans/repositories/plan_repository.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
lib/features/plans/services/plan_service.dart
Normal file
31
lib/features/plans/services/plan_service.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
161
lib/features/plans/services/plan_service.g.dart
Normal file
161
lib/features/plans/services/plan_service.g.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user