forked from eatvio/eatvio_app
feat(my_diets): fixed draft, active and personal diets creation, updating
This commit is contained in:
@@ -32,21 +32,21 @@ class CreateDietScreen extends StatelessWidget {
|
||||
const CreateDietScreen({super.key, this.diet, this.isEditing});
|
||||
|
||||
final Diet? diet;
|
||||
final bool? isEditing;
|
||||
final bool? isEditing;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<CreateDietCubit>()..setInitialData(diet),
|
||||
child: CreateDietScreenContent(diet: diet),
|
||||
child: CreateDietScreenContent(diet: diet, isEditing: isEditing),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CreateDietScreenContent extends StatefulWidget {
|
||||
const CreateDietScreenContent({super.key, this.diet});
|
||||
const CreateDietScreenContent({super.key, this.diet, this.isEditing});
|
||||
|
||||
final Diet? diet;
|
||||
|
||||
final bool? isEditing;
|
||||
@override
|
||||
State<CreateDietScreenContent> createState() => _CreateDietScreenContentState();
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках'),
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -182,14 +182,21 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
children: [
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
context.read<CreateDietCubit>().setDietState(DietState.draft);
|
||||
if (widget.diet != null) {
|
||||
if (widget.diet != null && widget.isEditing == true) {
|
||||
if (state.dietState == DietState.draft) {
|
||||
context.read<CreateDietCubit>().setDietState(DietState.active);
|
||||
} else {
|
||||
context.read<CreateDietCubit>().setDietState(DietState.draft);
|
||||
}
|
||||
final result = await context.read<CreateDietCubit>().updateDiet(widget.diet!.id);
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}');
|
||||
}
|
||||
} else {
|
||||
if (state.dietState != DietState.draft) {
|
||||
context.read<CreateDietCubit>().setDietState(DietState.draft);
|
||||
}
|
||||
final result = await context.read<CreateDietCubit>().createDiet();
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
@@ -197,15 +204,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
}
|
||||
}
|
||||
},
|
||||
// onPressed: state.isDietPublishing
|
||||
// ? () {}
|
||||
// : () async {
|
||||
// final diet = await context.read<CreateDietCubit>().getDietForPreview();
|
||||
// if (diet == null) return;
|
||||
// if (!context.mounted) return;
|
||||
// GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
|
||||
// },
|
||||
text: "В черновик",
|
||||
text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик",
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
buttonStyle: ButtonStyle(
|
||||
@@ -221,7 +220,9 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
final diet = await context.read<CreateDietCubit>().getDietForPreview();
|
||||
if (diet == null) return;
|
||||
if (!context.mounted) return;
|
||||
GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet);
|
||||
GoRouter.of(
|
||||
context,
|
||||
).pushNamed(DietPreviewScreen.name, extra: {'diet': diet, 'isImageInBase64': state.dietData.imageUrl == null});
|
||||
},
|
||||
text: s.preview,
|
||||
height: 52,
|
||||
@@ -237,13 +238,21 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
else
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
if (widget.diet != null) {
|
||||
final result = await context.read<CreateDietCubit>().updateDiet(
|
||||
widget.diet!.id,
|
||||
);
|
||||
if (widget.diet != null && widget.isEditing == true) {
|
||||
final result = await context.read<CreateDietCubit>().updateDiet(widget.diet!.id);
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}');
|
||||
} else {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isUpdated: true),
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
final result = await context.read<CreateDietCubit>().createDiet();
|
||||
@@ -253,7 +262,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
}
|
||||
}
|
||||
},
|
||||
text: widget.diet != null ? s.save : s.publish,
|
||||
text: widget.diet != null && widget.isEditing == true ? s.save : s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
|
||||
@@ -49,6 +49,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
type['carb'] = diet.type.carb ?? false;
|
||||
type['light'] = diet.type.light ?? false;
|
||||
final appConfig = sl<AppConfig>();
|
||||
String? imageUrl;
|
||||
if (diet.image?.startsWith('https') == true) {
|
||||
imageUrl = diet.image;
|
||||
} else if (diet.image != null && diet.image?.isNotEmpty == true) {
|
||||
imageUrl = '${appConfig.baseUrl}/storage/${diet.image}';
|
||||
}
|
||||
emit(
|
||||
state.copyWith.dietData(
|
||||
dailySchedule: dailySchedule,
|
||||
@@ -57,8 +63,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
name: diet.name,
|
||||
duration: diet.duration,
|
||||
type: type,
|
||||
// Avoid network image URLs during tests to prevent HTTP fetch attempts
|
||||
imageUrl: appConfig.buildType == AppBuildType.tests ? null : '${appConfig.baseUrl}/storage/${diet.image}',
|
||||
imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));
|
||||
@@ -196,7 +201,6 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
}
|
||||
|
||||
Future<Diet?> getDietForPreview() async {
|
||||
if (!_isFromValid()) return null;
|
||||
final authorProfile = await profileRepository.getMyProfile();
|
||||
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
|
||||
String image = base64.encode(imageBytes);
|
||||
@@ -305,7 +309,10 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
|
||||
Future<String?> createDiet() async {
|
||||
emit(state.copyWith(isDietPublishing: true));
|
||||
if (!_isFromValid()) {
|
||||
if ([DietState.active, DietState.personal].contains(state.dietState) && !_isFromValid()) {
|
||||
emit(state.copyWith(isDietPublishing: false));
|
||||
return 'validation';
|
||||
} else if (!_isFormValidForDraft()) {
|
||||
emit(state.copyWith(isDietPublishing: false));
|
||||
return 'validation';
|
||||
}
|
||||
@@ -324,12 +331,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
name: state.dietData.name ?? '',
|
||||
duration: state.dietData.duration ?? 7,
|
||||
state: dietState,
|
||||
image: image ?? file!,
|
||||
image: image ?? file,
|
||||
meatType: state.dietData.type['meat'] == true ? '1' : '0',
|
||||
carbType: state.dietData.type['carb'] == true ? '1' : '0',
|
||||
vegType: state.dietData.type['veg'] == true ? '1' : '0',
|
||||
lightType: state.dietData.type['light'] == true ? '1' : '0',
|
||||
details: state.dietData.details ?? '',
|
||||
details: state.dietData.details,
|
||||
dailySchedule: dailySchedule ?? [],
|
||||
);
|
||||
|
||||
@@ -343,7 +350,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
}
|
||||
|
||||
Future<String?> updateDiet(int dietId) async {
|
||||
// if (!_isFromValid()) return 'error';
|
||||
if ([DietState.active, DietState.personal].contains(state.dietState) && !_isFromValid()) {
|
||||
return 'error';
|
||||
} else if (!_isFormValidForDraft()) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
try {
|
||||
File? image;
|
||||
if (state.dietData.image != null) {
|
||||
@@ -359,7 +371,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
carbType: state.dietData.type['carb'] == true ? '1' : '0',
|
||||
vegType: state.dietData.type['veg'] == true ? '1' : '0',
|
||||
lightType: state.dietData.type['light'] == true ? '1' : '0',
|
||||
details: state.dietData.details,
|
||||
details: state.dietData.details?.isEmpty ?? true ? null : state.dietData.details,
|
||||
dailySchedule: state.dietData.dailySchedule,
|
||||
);
|
||||
return null;
|
||||
@@ -369,6 +381,24 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isFormValidForDraft() {
|
||||
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 != null && (state.dietData.details?.isNotEmpty ?? false) && (state.dietData.details?.length ?? 4) < 3) {
|
||||
result = false;
|
||||
emit(state.copyWith(dietDescriptionError: 'Длинна описания не может быть меньше 3 символов'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _isFromValid() {
|
||||
bool result = true;
|
||||
if ((state.dietData.name?.length ?? 0) < 3) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
@@ -18,10 +21,11 @@ import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class DietPreviewScreen extends StatefulWidget {
|
||||
final Diet diet;
|
||||
final bool isImageInBase64;
|
||||
late final List<int> selectedIndexes;
|
||||
static const name = 'createDietScreenPreview';
|
||||
static const path = '/create-diet-screen/preview';
|
||||
DietPreviewScreen({super.key, required this.diet}) {
|
||||
DietPreviewScreen({super.key, required this.diet, required this.isImageInBase64}) {
|
||||
selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0);
|
||||
}
|
||||
|
||||
@@ -35,7 +39,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.diet.image != null) {
|
||||
if (widget.diet.image != null && widget.isImageInBase64) {
|
||||
imageData = base64.decode(widget.diet.image!);
|
||||
}
|
||||
}
|
||||
@@ -109,6 +113,54 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (!widget.isImageInBase64 && widget.diet.image != null && (widget.diet.image?.isNotEmpty ?? false))
|
||||
Stack(
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: widget.diet.image?.startsWith('https') == true
|
||||
? widget.diet.image!
|
||||
: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.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(
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
children: [
|
||||
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
|
||||
const SizedBox(width: 8),
|
||||
Text(widget.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: InfoBouble(
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/heart.svg',
|
||||
width: 14,
|
||||
height: 12,
|
||||
colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
|
||||
@@ -185,7 +185,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
|
||||
quantity: int.parse(quantityController.text),
|
||||
name: glossary.name,
|
||||
productNutritionalValueId: glossary.id.toString(),
|
||||
productNutritionalValueId: glossary.id,
|
||||
portion: measurementUnits,
|
||||
);
|
||||
if (portionIndex == null) {
|
||||
@@ -212,7 +212,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
|
||||
quantity: int.parse(quantityController.text),
|
||||
name: newGlossary.name,
|
||||
productNutritionalValueId: newGlossary.id.toString(),
|
||||
productNutritionalValueId: newGlossary.id,
|
||||
portion: measurementUnits,
|
||||
);
|
||||
if (portionIndex == null) {
|
||||
|
||||
Reference in New Issue
Block a user