forked from eatvio/eatvio_app
feat(my_diets): draft diets fixes
This commit is contained in:
@@ -159,8 +159,11 @@ class OtherRoutes {
|
||||
path: CreateDietScreen.path,
|
||||
name: CreateDietScreen.name,
|
||||
builder: (context, state) {
|
||||
final diet = state.extra as Diet?;
|
||||
return CreateDietScreen(diet: diet);
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final diet = data['diet'] as Diet?;
|
||||
final isEditing = data['isEditing'] as bool?;
|
||||
return CreateDietScreen(diet: diet, isEditing: isEditing);
|
||||
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
|
||||
@@ -12,15 +12,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class InfoRow extends StatelessWidget {
|
||||
const InfoRow({
|
||||
required this.type,
|
||||
super.key,
|
||||
this.diet,
|
||||
this.itemsBackgroundColor,
|
||||
this.disableDescriptionOfTypes = false,
|
||||
this.plan,
|
||||
this.recipe,
|
||||
});
|
||||
const InfoRow({required this.type, super.key, this.diet, this.itemsBackgroundColor, this.disableDescriptionOfTypes = false, this.plan, this.recipe});
|
||||
final ProductType type;
|
||||
final Color? itemsBackgroundColor;
|
||||
final Diet? diet;
|
||||
@@ -50,44 +42,13 @@ class InfoRow extends StatelessWidget {
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/time2.svg',
|
||||
width: 13.3,
|
||||
height: 13.3,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/time2.svg', width: 13.3, height: 13.3),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
switch (type) {
|
||||
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
||||
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
|
||||
ProductType.recipe => timeTitle ?? '',
|
||||
},
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
switch (type) {
|
||||
ProductType.diet => 'assets/icons/people.svg',
|
||||
ProductType.plan => 'assets/icons/people.svg',
|
||||
ProductType.recipe => 'assets/icons/calories.svg',
|
||||
},
|
||||
width: 13.3,
|
||||
height: 13.3,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
switch (type) {
|
||||
ProductType.diet => '80 человек прошли',
|
||||
ProductType.plan => '80 человек прошли',
|
||||
ProductType.recipe => '${recipe?.calories.toStringAsFixed(0) ?? '0'} ккал',
|
||||
},
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(switch (type) {
|
||||
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
||||
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
|
||||
ProductType.recipe => timeTitle ?? '',
|
||||
}, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -95,11 +56,17 @@ class InfoRow extends StatelessWidget {
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/steps.svg',
|
||||
width: 13.3,
|
||||
height: 13.3,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/calories.svg', width: 13.3, height: 13.3),
|
||||
const SizedBox(width: 4),
|
||||
Text('${recipe?.calories.toStringAsFixed(0) ?? '0'} ккал', style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (type == ProductType.recipe)
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset('assets/icons/steps.svg', width: 13.3, height: 13.3),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
|
||||
@@ -110,13 +77,7 @@ class InfoRow extends StatelessWidget {
|
||||
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
TypeBadges(
|
||||
type: diet?.type,
|
||||
planType: plan?.type,
|
||||
disableDescriptionOfTypes: disableDescriptionOfTypes,
|
||||
),
|
||||
],
|
||||
children: [TypeBadges(type: diet?.type, planType: plan?.type, disableDescriptionOfTypes: disableDescriptionOfTypes)],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -29,10 +29,10 @@ class CreateDietScreen extends StatelessWidget {
|
||||
static const name = 'createDietScreen';
|
||||
static const path = '/create-diet-screen';
|
||||
|
||||
const CreateDietScreen({super.key, this.diet});
|
||||
const CreateDietScreen({super.key, this.diet, this.isEditing});
|
||||
|
||||
final Diet? diet;
|
||||
|
||||
final bool? isEditing;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
@@ -237,13 +237,23 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
else
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
final result = await context.read<CreateDietCubit>().createDiet();
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}');
|
||||
if (widget.diet != null) {
|
||||
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 {
|
||||
final result = await context.read<CreateDietCubit>().createDiet();
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}');
|
||||
}
|
||||
}
|
||||
},
|
||||
text: s.publish,
|
||||
text: widget.diet != null ? s.save : s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
|
||||
@@ -32,7 +32,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
|
||||
dietId = diet.id;
|
||||
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
|
||||
final countOfDays = dailyScheduleWithNutrition.length;
|
||||
final countOfDays = diet.duration;
|
||||
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
|
||||
|
||||
for (final dayEntry in dailyScheduleWithNutrition.entries) {
|
||||
@@ -199,7 +199,10 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
if (!_isFromValid()) return null;
|
||||
final authorProfile = await profileRepository.getMyProfile();
|
||||
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
|
||||
final imageInBase64 = base64.encode(imageBytes);
|
||||
String image = base64.encode(imageBytes);
|
||||
if (image.isEmpty && state.dietData.imageUrl != null) {
|
||||
image = state.dietData.imageUrl ?? '';
|
||||
}
|
||||
final carbs = state.dietData.dailySchedule?.map((element) {
|
||||
return element.fold<double>(0, (prev, element) {
|
||||
return prev +
|
||||
@@ -259,7 +262,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
id: 0,
|
||||
name: state.dietData.name ?? '',
|
||||
slug: '',
|
||||
image: imageInBase64,
|
||||
image: image.isNotEmpty ? image : null,
|
||||
duration: state.dietData.duration ?? 0,
|
||||
state: state.dietData.state ?? DietState.active,
|
||||
type: DietType(
|
||||
@@ -340,7 +343,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
}
|
||||
|
||||
Future<String?> updateDiet(int dietId) async {
|
||||
if (!_isFromValid()) return 'error';
|
||||
// if (!_isFromValid()) return 'error';
|
||||
try {
|
||||
File? image;
|
||||
if (state.dietData.image != null) {
|
||||
|
||||
@@ -31,11 +31,13 @@ class DietPreviewScreen extends StatefulWidget {
|
||||
|
||||
class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
late final Uint8List imageData;
|
||||
Uint8List? imageData;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
imageData = base64.decode(widget.diet.image);
|
||||
if (widget.diet.image != null) {
|
||||
imageData = base64.decode(widget.diet.image!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -58,7 +60,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.only(bottom: 160),
|
||||
padding: const EdgeInsets.only(bottom: 40),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -67,49 +69,47 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Image.memory(imageData, height: 300, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill),
|
||||
// CachedNetworkImage(
|
||||
// imageUrl: baseUrl + '/storage/' + widget.diet.image,
|
||||
// ),
|
||||
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(
|
||||
if (imageData != null)
|
||||
Stack(
|
||||
children: [
|
||||
Image.memory(imageData!, 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/heart.svg',
|
||||
width: 14,
|
||||
height: 12,
|
||||
colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
|
||||
const SizedBox(width: 8),
|
||||
Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -148,14 +148,6 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
},
|
||||
itemCount: widget.diet.dailySchedule.length,
|
||||
),
|
||||
const MainBackgroundBouble(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,12 +22,12 @@ abstract class Diet with _$Diet {
|
||||
@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: "image") required String? image,
|
||||
@JsonKey(name: "duration") required int duration,
|
||||
@JsonKey(name: "state") required DietState state,
|
||||
@JsonKey(name: "type") required DietType type,
|
||||
@JsonKey(name: "description_md") required String details,
|
||||
@JsonKey(name: "daily_schedule") required Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
|
||||
@JsonKey(name: "type", fromJson: parseDietTypeFromJson) required DietType type,
|
||||
@JsonKey(name: "description_md", defaultValue: '') required String details,
|
||||
@JsonKey(name: "daily_schedule", defaultValue: {}) required Map<int, Map<int, Map<int, DailySchedule>>> dailySchedule,
|
||||
@JsonKey(name: "author") required Author author,
|
||||
@JsonKey(name: "total_likes") required int totalLikes,
|
||||
@JsonKey(name: "total_views") required int totalViews,
|
||||
@@ -40,12 +40,20 @@ abstract class Diet with _$Diet {
|
||||
factory Diet.fromJson(Map<String, dynamic> json) => _$DietFromJson(json);
|
||||
}
|
||||
|
||||
DietType parseDietTypeFromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) {
|
||||
return const DietType(carb: false, light: false, meat: false, veg: false);
|
||||
}
|
||||
|
||||
return DietType.fromJson(json);
|
||||
}
|
||||
|
||||
enum DietState {
|
||||
@JsonKey(name: 'active')
|
||||
@JsonValue('active')
|
||||
active,
|
||||
@JsonKey(name: 'drafted')
|
||||
@JsonValue('drafted')
|
||||
draft,
|
||||
@JsonKey(name: 'personal')
|
||||
@JsonValue('personal')
|
||||
personal;
|
||||
|
||||
@override
|
||||
@@ -63,9 +71,7 @@ enum DietState {
|
||||
|
||||
@freezed
|
||||
abstract class CreatedDiet with _$CreatedDiet {
|
||||
const factory CreatedDiet({
|
||||
@JsonKey(name: "id") required int id,
|
||||
}) = _CreatedDiet;
|
||||
const factory CreatedDiet({@JsonKey(name: "id") required int id}) = _CreatedDiet;
|
||||
|
||||
factory CreatedDiet.fromJson(Map<String, dynamic> json) => _$CreatedDietFromJson(json);
|
||||
}
|
||||
@@ -135,10 +141,10 @@ enum Portion {
|
||||
}
|
||||
|
||||
String getValue() => switch (this) {
|
||||
Portion.grams => 'gr',
|
||||
Portion.milliliters => 'milliliters',
|
||||
Portion.pcs => 'pcs',
|
||||
};
|
||||
Portion.grams => 'gr',
|
||||
Portion.milliliters => 'milliliters',
|
||||
Portion.pcs => 'pcs',
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -62,14 +62,12 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
controller: switch (state.selectedStateIndex) {
|
||||
1 => draftDietsScrollController,
|
||||
2 => personalDietsScrollController,
|
||||
_ => activeDietsScrollController
|
||||
_ => activeDietsScrollController,
|
||||
},
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverAppBar(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
|
||||
backgroundColor: appTheme.gray100,
|
||||
foregroundColor: appTheme.gray100,
|
||||
automaticallyImplyLeading: false,
|
||||
@@ -79,20 +77,14 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
snap: true,
|
||||
scrolledUnderElevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(
|
||||
MediaQuery.sizeOf(context).width,
|
||||
64,
|
||||
),
|
||||
preferredSize: Size(MediaQuery.sizeOf(context).width, 64),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: MyDietsTab(),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: MyDietsTab()),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
@@ -105,23 +97,17 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
searchController: switch (state.selectedStateIndex) {
|
||||
1 => draftDietsSearchController,
|
||||
2 => personalDietsSearchController,
|
||||
_ => activeDietsSearchController
|
||||
_ => activeDietsSearchController,
|
||||
},
|
||||
onChanged: (query) {
|
||||
EasyDebounce.debounce(
|
||||
'searchDiets',
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
context.read<MyDietsCubit>().loadCurrentDiets(query: query == '' ? null : query);
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('searchDiets', const Duration(milliseconds: 500), () {
|
||||
context.read<MyDietsCubit>().loadCurrentDiets(query: query == '' ? null : query);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
AddItemButton(
|
||||
onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name),
|
||||
),
|
||||
AddItemButton(onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name)),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
@@ -138,14 +124,12 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
child: BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
builder: (BuildContext context, MyDietsState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
|
||||
} else if (state.error != null) {
|
||||
return DataErrorWidget(onReload: () => context.read<MyDietsCubit>().loadAllDiets());
|
||||
} else {
|
||||
final gotDietLoadingError = state.activeDietsError != null && state.selectedStateIndex == 0 ||
|
||||
final gotDietLoadingError =
|
||||
state.activeDietsError != null && state.selectedStateIndex == 0 ||
|
||||
state.draftDietsError != null && state.selectedStateIndex == 1 ||
|
||||
state.personalDietsError != null && state.selectedStateIndex == 2;
|
||||
|
||||
@@ -170,10 +154,7 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return MyDietWidget(
|
||||
diet: diets[index],
|
||||
key: ValueKey(diets[index].id),
|
||||
);
|
||||
return MyDietWidget(diet: diets[index], key: ValueKey(diets[index].id));
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 20);
|
||||
@@ -183,11 +164,7 @@ class MyDietsScreenContent extends StatelessWidget {
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: DietFilterBottomSheet(
|
||||
myDietsCubit: context.read<MyDietsCubit>(),
|
||||
),
|
||||
),
|
||||
child: FilterBadge(filterBottomSheet: DietFilterBottomSheet(myDietsCubit: context.read<MyDietsCubit>())),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.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';
|
||||
@@ -12,6 +11,7 @@ import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
@@ -36,6 +36,65 @@ class MyDietWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
void _editDiet(Diet diet) {
|
||||
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': true});
|
||||
}
|
||||
|
||||
void _suggestDiet(Diet diet) {
|
||||
GoRouter.of(context).pushNamed(PersonalDietSuggestionScreen.name, extra: diet.id);
|
||||
}
|
||||
|
||||
Future<void> _shareDiet(int dietId) async {
|
||||
await SharePlus.instance.share(ShareParams(uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/$dietId')));
|
||||
}
|
||||
|
||||
Future<void> _publishDiet(Diet diet, BuildContext context) async {
|
||||
final result = await context.read<MyDietsCubit>().publishDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
} else {
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
}
|
||||
}
|
||||
|
||||
void _unpublishDiet(Diet diet, BuildContext context) async {
|
||||
final result = await context.read<MyDietsCubit>().unpublishDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
height: 250,
|
||||
child: DietDraftedBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
} else {
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
}
|
||||
}
|
||||
|
||||
void _createBasedOnChosen(Diet diet, BuildContext context) async {
|
||||
await showErrorDialog(context, title: 'Предупреждение', description: 'Вы переходите к созданию новой диеты на основе скопированной');
|
||||
if (!context.mounted) return;
|
||||
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': false});
|
||||
}
|
||||
|
||||
void _deleteDiet(Diet diet, BuildContext context) async {
|
||||
final result = await context.read<MyDietsCubit>().deleteDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: DietDeletedBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -45,28 +104,34 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'navigateToComments': false}),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.image}',
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
if (widget.diet.image != null && (widget.diet.image?.isNotEmpty ?? false))
|
||||
Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (widget.diet.state == DietState.draft) {
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'navigateToComments': false});
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.image}',
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -81,19 +146,20 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
String details = widget.diet.details.length > 200
|
||||
? '${widget.diet.details.substring(0, 200)}...'
|
||||
: widget.diet.details;
|
||||
String details = widget.diet.details.length > 200 ? '${widget.diet.details.substring(0, 200)}...' : widget.diet.details;
|
||||
|
||||
return MarkdownHtmlWidget(
|
||||
markdownText: details,
|
||||
fontSize: TextStyles.textSMedium.fontSize ?? 14,
|
||||
textColor: appTheme.black,
|
||||
);
|
||||
return MarkdownHtmlWidget(markdownText: details, fontSize: TextStyles.textSMedium.fontSize ?? 14, textColor: appTheme.black);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.diet.image == null || (widget.diet.image?.isEmpty ?? true)) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.dividerColor, disableDescriptionOfTypes: true, type: ProductType.diet),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -109,21 +175,11 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
if (state.selectedStateIndex == 1) {
|
||||
final result = await context.read<MyDietsCubit>().publishDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
} else {
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
}
|
||||
_publishDiet(widget.diet, context);
|
||||
} else if (state.selectedStateIndex == 2) {
|
||||
GoRouter.of(context).pushNamed(PersonalDietSuggestionScreen.name, extra: widget.diet.id);
|
||||
_suggestDiet(widget.diet);
|
||||
} else {
|
||||
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet);
|
||||
_editDiet(widget.diet);
|
||||
}
|
||||
},
|
||||
text: switch (state.selectedStateIndex) {
|
||||
@@ -144,13 +200,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
const SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/${widget.diet.id}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
await _shareDiet(widget.diet.id);
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
@@ -169,8 +219,53 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
if (state.selectedStateIndex == 2) ...[
|
||||
const SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
_createBasedOnChosen(widget.diet, context);
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset(
|
||||
'assets/icons/copy2.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: ColorFilter.mode(appTheme.whiteA700, BlendMode.srcIn),
|
||||
),
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (state.selectedStateIndex == 1) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: widget.diet);
|
||||
},
|
||||
text: 'Превью',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
@@ -178,21 +273,9 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
final result = await context.read<MyDietsCubit>().unpublishDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
height: 250,
|
||||
child: DietDraftedBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
} else {
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
}
|
||||
_unpublishDiet(widget.diet, context);
|
||||
} else {
|
||||
await showErrorDialog(context, title: 'Предупреждение', description: 'Вы можете создать новую диету на основе существующей.');
|
||||
AppRouter.navigatorKey.currentContext?.pushNamed(CreateDietScreen.name, extra: widget.diet);
|
||||
_editDiet(widget.diet);
|
||||
}
|
||||
},
|
||||
text: switch (state.selectedStateIndex) {
|
||||
@@ -211,19 +294,23 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
const SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
final result = await context.read<MyDietsCubit>().deleteDiet(widget.diet.id);
|
||||
if (result) {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: DietDeletedBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
if (state.selectedStateIndex == 1) {
|
||||
_createBasedOnChosen(widget.diet, context);
|
||||
return;
|
||||
}
|
||||
_deleteDiet(widget.diet, context);
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20),
|
||||
leftIcon: switch (state.selectedStateIndex) {
|
||||
1 => SvgPicture.asset(
|
||||
'assets/icons/copy2.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: ColorFilter.mode(appTheme.gray, BlendMode.srcIn),
|
||||
),
|
||||
_ => SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20),
|
||||
},
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
@@ -245,4 +332,3 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user