diff --git a/analysis_options.yaml b/analysis_options.yaml
index 3d36fcec..e8fba18e 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,4 +1,11 @@
+
+linter:
+ rules:
+ - require_trailing_commas
+
analyzer:
+ exclude:
+ - '**/*.g.dart'
errors:
invalid_annotation_target: ignore
plugins:
diff --git a/assets/icons/apple.svg b/assets/icons/apple.svg
new file mode 100644
index 00000000..987a839b
--- /dev/null
+++ b/assets/icons/apple.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/arrow.svg b/assets/icons/arrow.svg
new file mode 100644
index 00000000..6ea3765b
--- /dev/null
+++ b/assets/icons/arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg
new file mode 100644
index 00000000..2101088f
--- /dev/null
+++ b/assets/icons/arrow_down.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/bread.svg b/assets/icons/bread.svg
new file mode 100644
index 00000000..c5e28365
--- /dev/null
+++ b/assets/icons/bread.svg
@@ -0,0 +1,19 @@
+
diff --git a/assets/icons/broccoli.svg b/assets/icons/broccoli.svg
new file mode 100644
index 00000000..e694e5a3
--- /dev/null
+++ b/assets/icons/broccoli.svg
@@ -0,0 +1,18 @@
+
diff --git a/assets/icons/close_button.svg b/assets/icons/close_button.svg
new file mode 100644
index 00000000..df54d212
--- /dev/null
+++ b/assets/icons/close_button.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/cocktail.svg b/assets/icons/cocktail.svg
new file mode 100644
index 00000000..5e0b7e93
--- /dev/null
+++ b/assets/icons/cocktail.svg
@@ -0,0 +1,16 @@
+
diff --git a/assets/icons/comment2.svg b/assets/icons/comment2.svg
new file mode 100644
index 00000000..de57faf3
--- /dev/null
+++ b/assets/icons/comment2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/complain.svg b/assets/icons/complain.svg
new file mode 100644
index 00000000..c6ef0cf4
--- /dev/null
+++ b/assets/icons/complain.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/copy2.svg b/assets/icons/copy2.svg
new file mode 100644
index 00000000..5049a094
--- /dev/null
+++ b/assets/icons/copy2.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/icons/eye.svg b/assets/icons/eye.svg
new file mode 100644
index 00000000..9e56e334
--- /dev/null
+++ b/assets/icons/eye.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg
new file mode 100644
index 00000000..b0e7a93d
--- /dev/null
+++ b/assets/icons/heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/meat2.svg b/assets/icons/meat2.svg
new file mode 100644
index 00000000..d8c25b41
--- /dev/null
+++ b/assets/icons/meat2.svg
@@ -0,0 +1,29 @@
+
diff --git a/assets/icons/more_button.svg b/assets/icons/more_button.svg
new file mode 100644
index 00000000..d22c4b4a
--- /dev/null
+++ b/assets/icons/more_button.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/people.svg b/assets/icons/people.svg
new file mode 100644
index 00000000..938109c1
--- /dev/null
+++ b/assets/icons/people.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/share2.svg b/assets/icons/share2.svg
new file mode 100644
index 00000000..a2eb2f77
--- /dev/null
+++ b/assets/icons/share2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/time2.svg b/assets/icons/time2.svg
new file mode 100644
index 00000000..aefa127b
--- /dev/null
+++ b/assets/icons/time2.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/app_router.dart b/lib/app_router.dart
index d88156f1..6b8edb3c 100644
--- a/lib/app_router.dart
+++ b/lib/app_router.dart
@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
+import 'package:eatvio_app/features/complaining/presentation/complaining_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
@@ -274,6 +275,11 @@ class AppRouter {
index: int.parse(state.pathParameters['index']!),
),
),
+ GoRoute(
+ path: '/complaining-screen',
+ name: 'complainingScreen',
+ builder: (context, state) => ComplainingScreen(),
+ ),
],
);
}
diff --git a/lib/core/initialization_cubit/initialization_cubit.dart b/lib/core/initialization_cubit/initialization_cubit.dart
index da7588cd..93e02a47 100644
--- a/lib/core/initialization_cubit/initialization_cubit.dart
+++ b/lib/core/initialization_cubit/initialization_cubit.dart
@@ -89,11 +89,11 @@ class InitializationCubit extends Cubit {
emit(InitializationState.success(
client: client,
- ));
+ ),);
} catch (e) {
emit(InitializationState.error(
errorMessage: e.toString(),
- ));
+ ),);
rethrow;
}
}
diff --git a/lib/core/utils/localization_utils.dart b/lib/core/utils/localization_utils.dart
new file mode 100644
index 00000000..23d846a0
--- /dev/null
+++ b/lib/core/utils/localization_utils.dart
@@ -0,0 +1,43 @@
+import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+
+String getLocalizedMeasurementUnits(S s, Portion portion) {
+ switch (portion) {
+ case Portion.GRAMS:
+ return s.gr;
+ case Portion.MILLILITERS:
+ return s.milliliter;
+ case Portion.PCS:
+ return s.pcs;
+ }
+}
+
+String getDayForm(int number, S s) {
+ if (number % 100 >= 11 && number % 100 <= 14) {
+ return s.days;
+ }
+
+ int lastDigit = number % 10;
+ if (lastDigit == 1) {
+ return s.day.toLowerCase();
+ } else if (lastDigit >= 2 && lastDigit <= 4) {
+ return s.another_form_of_day;
+ } else {
+ return s.days;
+ }
+}
+
+String getCommentForm(int number, S s) {
+ if (number % 100 >= 11 && number % 100 <= 14) {
+ return s.comments_plural;
+ }
+
+ int lastDigit = number % 10;
+ if (lastDigit == 1) {
+ return s.comment;
+ } else if (lastDigit >= 2 && lastDigit <= 4) {
+ return s.another_form_of_comment;
+ } else {
+ return s.comments_plural;
+ }
+}
diff --git a/lib/core/utils/string_extentions.dart b/lib/core/utils/string_extentions.dart
new file mode 100644
index 00000000..4d0e5173
--- /dev/null
+++ b/lib/core/utils/string_extentions.dart
@@ -0,0 +1,9 @@
+
+
+
+extension Capitalization on String {
+
+ String capitalize() {
+ return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
+ }
+}
\ No newline at end of file
diff --git a/lib/core/utils/toasts.dart b/lib/core/utils/toasts.dart
new file mode 100644
index 00000000..18195ccd
--- /dev/null
+++ b/lib/core/utils/toasts.dart
@@ -0,0 +1,15 @@
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+
+const int _toastDuration = 2;
+
+void showInfoToast(String message) {
+ Fluttertoast.showToast(
+ msg: message,
+ toastLength: Toast.LENGTH_SHORT,
+ gravity: ToastGravity.TOP,
+ timeInSecForIosWeb: _toastDuration,
+ backgroundColor: appTheme.blueA400,
+ textColor: appTheme.whiteA700,
+ fontSize: 16.0,);
+}
diff --git a/lib/core/widgets/base_button.dart b/lib/core/widgets/base_button.dart
new file mode 100644
index 00000000..c632ff4d
--- /dev/null
+++ b/lib/core/widgets/base_button.dart
@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+class BaseButton extends StatelessWidget {
+ const BaseButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return FilledButton(onPressed: () {}, child: Text('qweqwe'));
+ }
+}
diff --git a/lib/core/widgets/custom_divider.dart b/lib/core/widgets/custom_divider.dart
new file mode 100644
index 00000000..8262ee4a
--- /dev/null
+++ b/lib/core/widgets/custom_divider.dart
@@ -0,0 +1,14 @@
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+
+class CustomDivider extends StatelessWidget {
+ const CustomDivider({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Divider(
+ color: appTheme.dividerColor,
+ height: 1,
+ );
+ }
+}
diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
index 629e3d26..2990a507 100644
--- a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
+++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
@@ -13,7 +13,7 @@ class ProfileAvatarCubit extends Cubit {
try {
emit(ProfileAvatarState.loaded(
profile: await profileRepository.getMyProfile(),
- ));
+ ),);
} catch (e) {
emit(ProfileAvatarState.error(errorMessage: e.toString()));
}
diff --git a/lib/features/calendar/presentation/pages/calendar_screen.dart b/lib/features/calendar/presentation/pages/calendar_screen.dart
index cf9ca83e..329ad7ec 100644
--- a/lib/features/calendar/presentation/pages/calendar_screen.dart
+++ b/lib/features/calendar/presentation/pages/calendar_screen.dart
@@ -89,7 +89,7 @@ class CalendarScreen extends StatelessWidget {
),
),
),
- ]);
+ ],);
}
_buildCalendarBody(BuildContext context, CalendarStateLoaded state) {
@@ -225,7 +225,7 @@ class CalendarScreen extends StatelessWidget {
style: CustomTextStyles.titleSmallBold, // Предполагается, что текст белый
),
),
- ]),
+ ],),
],
),
),
diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
index ef4a3f3a..2ee24863 100644
--- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
+++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
@@ -38,7 +38,7 @@ class ChatRoomCubit extends Cubit {
chatMessages: (state as ChatRoomStateLoaded).chatMessages,
isTyping: (state as ChatRoomStateLoaded).isTyping,
isLoading: isLoading,
- ));
+ ),);
try {
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1);
@@ -49,7 +49,7 @@ class ChatRoomCubit extends Cubit {
chatMessages: [...(state as ChatRoomStateLoaded).chatMessages, ...chatMessages.messagesList],
isTyping: (state as ChatRoomStateLoaded).isTyping,
isLoading: isLoading,
- ));
+ ),);
currentPage++;
} catch (e) {
@@ -72,7 +72,7 @@ class ChatRoomCubit extends Cubit {
chatMessages: chatMessages.messagesList,
isTyping: false,
isLoading: isLoading,
- ));
+ ),);
} catch (e) {
emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
}
diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart
index 6c119f19..b190cb27 100644
--- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart
+++ b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart
@@ -14,7 +14,7 @@ class ChatSelectCubit extends Cubit {
try {
emit(ChatSelectState.loaded(
chatRooms: (await chatRepository.getChatRoomPreviews(query: query)),
- ));
+ ),);
} catch (e) {
emit(
ChatSelectState.error(errorMessage: e.toString()),
diff --git a/lib/features/complaining/presentation/complaining_screen.dart b/lib/features/complaining/presentation/complaining_screen.dart
new file mode 100644
index 00000000..2c3e0fd3
--- /dev/null
+++ b/lib/features/complaining/presentation/complaining_screen.dart
@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+class ComplainingScreen extends StatelessWidget {
+ const ComplainingScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Placeholder();
+ }
+}
\ No newline at end of file
diff --git a/lib/features/diets/presentation/bloc/about_diet_cubit.dart b/lib/features/diets/presentation/bloc/about_diet_cubit.dart
new file mode 100644
index 00000000..830dd3a6
--- /dev/null
+++ b/lib/features/diets/presentation/bloc/about_diet_cubit.dart
@@ -0,0 +1,115 @@
+import 'dart:async';
+import 'dart:developer';
+
+import 'package:easy_debounce/easy_debounce.dart';
+import 'package:eatvio_app/features/diets/presentation/bloc/about_diet_state.dart';
+import 'package:eatvio_app/features/diets/repositories/diets_repository.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class AboutDietCubit extends Cubit {
+ AboutDietCubit({required this.dietsRepository}) : super(AboutDietState.initial());
+ final DietsRepository dietsRepository;
+
+ Future getDiet(int id) async {
+ try {
+ final diet = await dietsRepository.getDiet(id);
+ emit(AboutDietState.loaded(diet));
+ } catch (e) {
+ log(e.toString());
+ }
+ }
+
+ Future getDietWithLoading(int id) async{
+ try {
+ emit(AboutDietState.loading());
+ final diet = await dietsRepository.getDiet(id);
+ emit(AboutDietState.loaded(diet));
+ } catch (e) {
+ log(e.toString());
+ }
+ }
+
+ Future likeDiet(int id) async {
+ if (state is! DietStateLoaded) return;
+
+ final currentDiet = (state as DietStateLoaded).diet;
+
+ if (currentDiet.wasLikedByMe) {
+ final updatedDiet = currentDiet.copyWith(
+ totalLikes: currentDiet.totalLikes - 1,
+ wasLikedByMe: false,
+ );
+ emit(AboutDietState.loaded(updatedDiet));
+ } else {
+ final updatedDiet = currentDiet.copyWith(
+ totalLikes: currentDiet.totalLikes + 1,
+ wasLikedByMe: true,
+ );
+ emit(AboutDietState.loaded(updatedDiet));
+ }
+
+ EasyDebounce.debounce(
+ 'like_diet',
+ Duration(seconds: 1),
+ () async {
+ try {
+ await dietsRepository.likeDiet(id);
+ await getDiet(id);
+ } catch (e) {
+ log(e.toString());
+ emit(AboutDietState.loaded(currentDiet));
+ }
+ },
+ );
+ }
+
+ Future addToFavorite(int id) async {
+ if (state is! DietStateLoaded) return;
+
+ final currentDiet = (state as DietStateLoaded).diet;
+
+ final updatedDiet = currentDiet.copyWith(
+ isFavorite: true,
+ );
+ emit(AboutDietState.loaded(updatedDiet));
+
+ EasyDebounce.debounce(
+ 'add_diet_to_favorites',
+ Duration(seconds: 1),
+ () async {
+ try {
+ await dietsRepository.addToFavorite(id);
+ await getDiet(id);
+ } catch (e) {
+ log(e.toString());
+ emit(AboutDietState.loaded(currentDiet));
+ }
+ },
+ );
+ }
+
+ Future removeFromFavorite(int id) async {
+ if (state is! DietStateLoaded) return;
+
+ final currentDiet = (state as DietStateLoaded).diet;
+
+ final updatedDiet = currentDiet.copyWith(
+ isFavorite: false,
+ );
+ emit(AboutDietState.loaded(updatedDiet));
+
+ EasyDebounce.debounce(
+ 'remove_diet_from_favorites',
+ Duration(seconds: 1),
+ () async {
+ try {
+ await dietsRepository.removeFromFavorite(id);
+ await getDiet(id);
+ } catch (e) {
+ log(e.toString());
+ emit(AboutDietState.loaded(currentDiet));
+ }
+ },
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/bloc/about_diet_state.dart b/lib/features/diets/presentation/bloc/about_diet_state.dart
new file mode 100644
index 00000000..36a78a31
--- /dev/null
+++ b/lib/features/diets/presentation/bloc/about_diet_state.dart
@@ -0,0 +1,14 @@
+import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'about_diet_state.freezed.dart';
+
+@freezed
+class AboutDietState with _$AboutDietState {
+ const factory AboutDietState() = _AboutDietState;
+
+ const factory AboutDietState.initial() = DietStateInitial;
+ const factory AboutDietState.loading() = DietStateLoading;
+ const factory AboutDietState.loaded(Diet diet) = DietStateLoaded;
+ const factory AboutDietState.error({required String message}) = DietStateError;
+}
diff --git a/lib/features/diets/presentation/bloc/about_diet_state.freezed.dart b/lib/features/diets/presentation/bloc/about_diet_state.freezed.dart
new file mode 100644
index 00000000..ce2270dc
--- /dev/null
+++ b/lib/features/diets/presentation/bloc/about_diet_state.freezed.dart
@@ -0,0 +1,796 @@
+// 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 'about_diet_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(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 _$AboutDietState {
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $AboutDietStateCopyWith<$Res> {
+ factory $AboutDietStateCopyWith(
+ AboutDietState value, $Res Function(AboutDietState) then) =
+ _$AboutDietStateCopyWithImpl<$Res, AboutDietState>;
+}
+
+/// @nodoc
+class _$AboutDietStateCopyWithImpl<$Res, $Val extends AboutDietState>
+ implements $AboutDietStateCopyWith<$Res> {
+ _$AboutDietStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+abstract class _$$AboutDietStateImplCopyWith<$Res> {
+ factory _$$AboutDietStateImplCopyWith(_$AboutDietStateImpl value,
+ $Res Function(_$AboutDietStateImpl) then) =
+ __$$AboutDietStateImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$AboutDietStateImplCopyWithImpl<$Res>
+ extends _$AboutDietStateCopyWithImpl<$Res, _$AboutDietStateImpl>
+ implements _$$AboutDietStateImplCopyWith<$Res> {
+ __$$AboutDietStateImplCopyWithImpl(
+ _$AboutDietStateImpl _value, $Res Function(_$AboutDietStateImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$AboutDietStateImpl implements _AboutDietState {
+ const _$AboutDietStateImpl();
+
+ @override
+ String toString() {
+ return 'AboutDietState()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$AboutDietStateImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) {
+ return $default();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) {
+ return $default?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) {
+ if ($default != null) {
+ return $default();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) {
+ return $default(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) {
+ return $default?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) {
+ if ($default != null) {
+ return $default(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _AboutDietState implements AboutDietState {
+ const factory _AboutDietState() = _$AboutDietStateImpl;
+}
+
+/// @nodoc
+abstract class _$$DietStateInitialImplCopyWith<$Res> {
+ factory _$$DietStateInitialImplCopyWith(_$DietStateInitialImpl value,
+ $Res Function(_$DietStateInitialImpl) then) =
+ __$$DietStateInitialImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$DietStateInitialImplCopyWithImpl<$Res>
+ extends _$AboutDietStateCopyWithImpl<$Res, _$DietStateInitialImpl>
+ implements _$$DietStateInitialImplCopyWith<$Res> {
+ __$$DietStateInitialImplCopyWithImpl(_$DietStateInitialImpl _value,
+ $Res Function(_$DietStateInitialImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$DietStateInitialImpl implements DietStateInitial {
+ const _$DietStateInitialImpl();
+
+ @override
+ String toString() {
+ return 'AboutDietState.initial()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$DietStateInitialImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) {
+ return initial();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) {
+ return initial?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) {
+ return initial(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) {
+ return initial?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class DietStateInitial implements AboutDietState {
+ const factory DietStateInitial() = _$DietStateInitialImpl;
+}
+
+/// @nodoc
+abstract class _$$DietStateLoadingImplCopyWith<$Res> {
+ factory _$$DietStateLoadingImplCopyWith(_$DietStateLoadingImpl value,
+ $Res Function(_$DietStateLoadingImpl) then) =
+ __$$DietStateLoadingImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$DietStateLoadingImplCopyWithImpl<$Res>
+ extends _$AboutDietStateCopyWithImpl<$Res, _$DietStateLoadingImpl>
+ implements _$$DietStateLoadingImplCopyWith<$Res> {
+ __$$DietStateLoadingImplCopyWithImpl(_$DietStateLoadingImpl _value,
+ $Res Function(_$DietStateLoadingImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$DietStateLoadingImpl implements DietStateLoading {
+ const _$DietStateLoadingImpl();
+
+ @override
+ String toString() {
+ return 'AboutDietState.loading()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$DietStateLoadingImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) {
+ return loading();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) {
+ return loading?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) {
+ return loading(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) {
+ return loading?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class DietStateLoading implements AboutDietState {
+ const factory DietStateLoading() = _$DietStateLoadingImpl;
+}
+
+/// @nodoc
+abstract class _$$DietStateLoadedImplCopyWith<$Res> {
+ factory _$$DietStateLoadedImplCopyWith(_$DietStateLoadedImpl value,
+ $Res Function(_$DietStateLoadedImpl) then) =
+ __$$DietStateLoadedImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({Diet diet});
+
+ $DietCopyWith<$Res> get diet;
+}
+
+/// @nodoc
+class __$$DietStateLoadedImplCopyWithImpl<$Res>
+ extends _$AboutDietStateCopyWithImpl<$Res, _$DietStateLoadedImpl>
+ implements _$$DietStateLoadedImplCopyWith<$Res> {
+ __$$DietStateLoadedImplCopyWithImpl(
+ _$DietStateLoadedImpl _value, $Res Function(_$DietStateLoadedImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? diet = null,
+ }) {
+ return _then(_$DietStateLoadedImpl(
+ null == diet
+ ? _value.diet
+ : diet // ignore: cast_nullable_to_non_nullable
+ as Diet,
+ ));
+ }
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $DietCopyWith<$Res> get diet {
+ return $DietCopyWith<$Res>(_value.diet, (value) {
+ return _then(_value.copyWith(diet: value));
+ });
+ }
+}
+
+/// @nodoc
+
+class _$DietStateLoadedImpl implements DietStateLoaded {
+ const _$DietStateLoadedImpl(this.diet);
+
+ @override
+ final Diet diet;
+
+ @override
+ String toString() {
+ return 'AboutDietState.loaded(diet: $diet)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DietStateLoadedImpl &&
+ (identical(other.diet, diet) || other.diet == diet));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, diet);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DietStateLoadedImplCopyWith<_$DietStateLoadedImpl> get copyWith =>
+ __$$DietStateLoadedImplCopyWithImpl<_$DietStateLoadedImpl>(
+ this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) {
+ return loaded(diet);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) {
+ return loaded?.call(diet);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) {
+ if (loaded != null) {
+ return loaded(diet);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) {
+ return loaded(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) {
+ return loaded?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) {
+ if (loaded != null) {
+ return loaded(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class DietStateLoaded implements AboutDietState {
+ const factory DietStateLoaded(final Diet diet) = _$DietStateLoadedImpl;
+
+ Diet get diet;
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DietStateLoadedImplCopyWith<_$DietStateLoadedImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$$DietStateErrorImplCopyWith<$Res> {
+ factory _$$DietStateErrorImplCopyWith(_$DietStateErrorImpl value,
+ $Res Function(_$DietStateErrorImpl) then) =
+ __$$DietStateErrorImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String message});
+}
+
+/// @nodoc
+class __$$DietStateErrorImplCopyWithImpl<$Res>
+ extends _$AboutDietStateCopyWithImpl<$Res, _$DietStateErrorImpl>
+ implements _$$DietStateErrorImplCopyWith<$Res> {
+ __$$DietStateErrorImplCopyWithImpl(
+ _$DietStateErrorImpl _value, $Res Function(_$DietStateErrorImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? message = null,
+ }) {
+ return _then(_$DietStateErrorImpl(
+ message: null == message
+ ? _value.message
+ : message // ignore: cast_nullable_to_non_nullable
+ as String,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$DietStateErrorImpl implements DietStateError {
+ const _$DietStateErrorImpl({required this.message});
+
+ @override
+ final String message;
+
+ @override
+ String toString() {
+ return 'AboutDietState.error(message: $message)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DietStateErrorImpl &&
+ (identical(other.message, message) || other.message == message));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, message);
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DietStateErrorImplCopyWith<_$DietStateErrorImpl> get copyWith =>
+ __$$DietStateErrorImplCopyWithImpl<_$DietStateErrorImpl>(
+ this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when(
+ TResult Function() $default, {
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function(Diet diet) loaded,
+ required TResult Function(String message) error,
+ }) {
+ return error(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull(
+ TResult? Function()? $default, {
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function(Diet diet)? loaded,
+ TResult? Function(String message)? error,
+ }) {
+ return error?.call(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen(
+ TResult Function()? $default, {
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function(Diet diet)? loaded,
+ TResult Function(String message)? error,
+ required TResult orElse(),
+ }) {
+ if (error != null) {
+ return error(message);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map(
+ TResult Function(_AboutDietState value) $default, {
+ required TResult Function(DietStateInitial value) initial,
+ required TResult Function(DietStateLoading value) loading,
+ required TResult Function(DietStateLoaded value) loaded,
+ required TResult Function(DietStateError value) error,
+ }) {
+ return error(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull(
+ TResult? Function(_AboutDietState value)? $default, {
+ TResult? Function(DietStateInitial value)? initial,
+ TResult? Function(DietStateLoading value)? loading,
+ TResult? Function(DietStateLoaded value)? loaded,
+ TResult? Function(DietStateError value)? error,
+ }) {
+ return error?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap(
+ TResult Function(_AboutDietState value)? $default, {
+ TResult Function(DietStateInitial value)? initial,
+ TResult Function(DietStateLoading value)? loading,
+ TResult Function(DietStateLoaded value)? loaded,
+ TResult Function(DietStateError value)? error,
+ required TResult orElse(),
+ }) {
+ if (error != null) {
+ return error(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class DietStateError implements AboutDietState {
+ const factory DietStateError({required final String message}) =
+ _$DietStateErrorImpl;
+
+ String get message;
+
+ /// Create a copy of AboutDietState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DietStateErrorImplCopyWith<_$DietStateErrorImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart
index 7dd66a37..db0bd378 100644
--- a/lib/features/diets/presentation/pages/about_diet_screen.dart
+++ b/lib/features/diets/presentation/pages/about_diet_screen.dart
@@ -2,17 +2,31 @@ 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/custom_divider.dart';
+import 'package:eatvio_app/features/diets/presentation/bloc/about_diet_cubit.dart';
+import 'package:eatvio_app/features/diets/presentation/bloc/about_diet_state.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/cpfc_container.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/features/diets/presentation/widgets/main_background_bouble.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/start_diet_bottom_sheet.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart';
import 'package:eatvio_app/features/feed/models/diets_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/generated/l10n.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_html/flutter_html.dart';
-import 'package:go_router/go_router.dart';
-import 'package:rive/rive.dart';
+import 'package:flutter_portal/flutter_portal.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:rive/rive.dart' hide LinearGradient;
class AboutDietScreen extends StatefulWidget {
final Diet diet;
@@ -28,333 +42,490 @@ class AboutDietScreen extends StatefulWidget {
class _AboutDietScreenState extends State {
final CommentsCubit commentsCubit = sl();
+ final AboutDietCubit dietsCubit = sl();
int? replyId;
+ final ScrollController scrollController = ScrollController();
final textEditingController = TextEditingController();
+ bool isMoreOptionsVisible = false;
+
+ @override
+ void initState() {
+ super.initState();
+ scrollController.addListener(() {
+ if (isMoreOptionsVisible) {
+ setState(() {
+ isMoreOptionsVisible = false;
+ });
+ }
+ });
+ }
@override
Widget build(BuildContext context) {
- return BlocProvider(
- create: (BuildContext context) => commentsCubit
- ..getComments(
- widget.diet.id.toString(),
- CommentSourceType.diet,
- true,
- ),
- child: BlocConsumer(
- listener: (BuildContext context, CommentsState state) {
- if (state is CommentsStateLoaded) {
- setState(() {
- replyId = state.replyId;
- });
- }
- },
- builder: (BuildContext context, CommentsState state) {
- return Scaffold(
- 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: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- SizedBox(
- width: MediaQuery.of(context).size.width - 24,
- child: FloatingActionButton.extended(
- heroTag: 'nextButtonRegistration',
- highlightElevation: 0,
- elevation: 0,
- focusElevation: 0,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(16),
- ),
- backgroundColor: appTheme.greenA700,
- onPressed: () {},
- label: Text(
- 'Начать диету',
- style: theme.textTheme.titleSmall,
- ),
- ),
- ),
- SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (replyId != null)
- GestureDetector(
- onTap: () {
- context.read().setReplyId(null);
- setState(() {
- replyId = null;
- textEditingController.clear();
- });
- FocusManager.instance.primaryFocus?.unfocus();
- },
- child: Container(
- decoration: BoxDecoration(
- color: appTheme.blueA400,
- shape: BoxShape.circle,
+ final s = S.of(context);
+ return BlocProvider(
+ create: (context) => dietsCubit..getDietWithLoading(widget.diet.id),
+ child: BlocProvider(
+ create: (BuildContext context) => commentsCubit
+ ..getComments(
+ widget.diet.id.toString(),
+ CommentSourceType.diet,
+ true,
+ ),
+ child: BlocConsumer(
+ listener: (BuildContext context, CommentsState commentsState) {
+ if (commentsState is CommentsStateLoaded) {
+ setState(() {
+ replyId = commentsState.replyId;
+ });
+ }
+ },
+ builder: (BuildContext context, CommentsState state) {
+ return BlocBuilder(
+ builder: (context, dietState) {
+ return dietState is DietStateLoaded
+ ? 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: () {
+ showModalBottomSheet(context: context, builder: (context) => StartDietBottomSheet());
+ },
+ label: Text(
+ s.startDiet,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ color: appTheme.whiteA700,
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(width: 8),
+ GestureDetector(
+ onTap: () {
+ if (dietState.diet.isFavorite) {
+ dietsCubit.removeFromFavorite(widget.diet.id);
+ } else {
+ dietsCubit.addToFavorite(widget.diet.id);
+ }
+ },
+ child: Container(
+ width: 52,
+ height: 52,
+ decoration: BoxDecoration(color: appTheme.gray50, shape: BoxShape.circle),
+ child: Icon(
+ dietState.diet.isFavorite ? Icons.bookmark : Icons.bookmark_border_rounded,
+ color: appTheme.blueGray800,
+ size: 24,
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 8),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (replyId != null)
+ GestureDetector(
+ onTap: () {
+ context.read().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: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: Colors.black,
+ height: 1,
+ ),
+ decoration: InputDecoration(
+ isDense: true,
+ contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ hintText: s.comment.capitalize(),
+ hintStyle: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ height: 1,
+ color: Color(0xFFAEAEB2),
+ ),
+ 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.diet.id.toString(),
+ commentText: textEditingController.text,
+ parentCommentId: replyId,
+ commentSourceType: CommentSourceType.diet,
+ );
+ context.read().setReplyId(null);
+ setState(() {
+ textEditingController.clear();
+ replyId = null;
+ });
+ dietsCubit.getDiet(widget.diet.id);
+ 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.blueGray800,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ appBar: AppBar(
+ centerTitle: true,
+ title: Text(
+ widget.diet.name,
+ maxLines: 2,
+ textAlign: TextAlign.center,
+ overflow: TextOverflow.ellipsis,
+ softWrap: true,
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 24,
+ fontWeight: FontWeight.w500,
+ height: 1,
+ ),
+ ),
+ actions: [
+ PortalTarget(
+ visible: isMoreOptionsVisible,
+ portalFollower: MoreOptions(id: widget.diet.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.diet.image,
+ ),
+ 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(
+ dietState.diet.totalViews.toString(),
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500),
+ ),
+ ],
+ backgroundColor: appTheme.whiteA700,
+ ),
+ SizedBox(width: 12),
+ GestureDetector(
+ onTap: () {
+ dietsCubit.likeDiet(widget.diet.id);
+ },
+ child: InfoBouble(
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/heart.svg',
+ width: 14,
+ height: 12,
+ colorFilter: dietState.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
+ ),
+ SizedBox(width: 8),
+ Text(
+ dietState.diet.totalLikes.toString(),
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500),
+ ),
+ ],
+ backgroundColor: appTheme.whiteA700,
+ ),
+ ),
+ SizedBox(width: 12),
+ InfoBouble(
+ children: [
+ SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
+ SizedBox(width: 8),
+ Text(
+ dietState.diet.totalComments.toString(),
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500),
+ ),
+ ],
+ backgroundColor: appTheme.whiteA700,
+ ),
+ ],
+ ),
+ ),
+ ],),
+ SizedBox(height: 16),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Text(
+ s.about_diet,
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 20, fontWeight: FontWeight.w600),
+ ),
+ ),
+ SizedBox(height: 12),
+ SingleChildScrollView(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ children: [
+ InfoBouble(
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/time2.svg',
+ width: 13.3,
+ height: 13.3,
+ ),
+ SizedBox(width: 4),
+ Text(widget.diet.duration.toString() + ' ' + getDayForm(widget.diet.duration, s),
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500),),
+ ],
+ ),
+ SizedBox(width: 12),
+ InfoBouble(
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/people.svg',
+ width: 13.3,
+ height: 13.3,
+ ),
+ SizedBox(width: 4),
+ Text('80 человек прошли(статика, ещё нет бека)',
+ style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500),),
+ ],
+ ),
+ SizedBox(width: 12),
+ InfoBouble(
+ children: [
+ TypeBadges(type: widget.diet.type),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ExpandableText(
+ text: widget.diet.details,
+ ),
+ SizedBox(height: 20),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: AuthorCard(
+ author: widget.diet.author,
+ ),
+ ),
+ SizedBox(height: 16),
+ ],
+ ),
+ ),
+ SizedBox(height: 16),
+ MainBackgroundBouble(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 16.0),
+ child: Center(
+ child: Text(
+ s.diet_plan + '(${widget.diet.duration.toString()} ' + getDayForm(widget.diet.duration, s) + ')',
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 20,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ),
+ ),),
+ SizedBox(height: 8),
+ ListView.builder(
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ return _dayOfPlan(widget.diet.dailySchedule[index], index, s);
+ },
+ itemCount: widget.diet.dailySchedule.length,
+ ),
+ MainBackgroundBouble(
+ padding: EdgeInsets.symmetric(vertical: 16),
+ child: Column(children: [
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ alignment: Alignment.centerLeft,
+ child: Text(
+ dietState.diet.totalComments.toString() + ' ' + getCommentForm(widget.diet.totalComments, s),
+ style: TextStyle(
+ fontSize: 12,
+ color: appTheme.gray500,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ),
+ SizedBox(height: 16),
+ if (state is CommentsStateLoading)
+ Padding(
+ padding: const EdgeInsets.all(32),
+ child: SizedBox(
+ height: 32,
+ width: 32,
+ child: RiveAnimation.asset(
+ 'assets/rive_animations/loading.riv',
+ ),
+ ),
+ ),
+ if (state is CommentsStateLoaded)
+ CommentsWidget(
+ id: widget.diet.id.toString(),
+ commentSourceType: CommentSourceType.diet,
+ comment: state.comment,
+ replyId: replyId,
+ ),
+ ],),
+ ),
+ ],
+ ),
+ ),
+ )
+ : Scaffold(
+ body: Center(
+ child: SizedBox(
height: 32,
width: 32,
- child: Icon(
- Icons.close_rounded,
- size: 18,
- color: Colors.white,
+ child: RiveAnimation.asset(
+ 'assets/rive_animations/loading.riv',
),
),
),
- if (replyId != null) SizedBox(width: 8),
- SizedBox(
- width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
- child: TextFormField(
- onChanged: (_) {
- setState(() {});
- },
- controller: textEditingController,
- maxLines: 5,
- minLines: 1,
- keyboardType: TextInputType.multiline,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w400,
- color: Colors.black,
- height: 1,
- ),
- decoration: InputDecoration(
- isDense: true,
- contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
- hintText: 'Комментарий',
- hintStyle: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w400,
- height: 1,
- color: Color(0xFFAEAEB2),
- ),
- 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.diet.id.toString(),
- commentText: textEditingController.text,
- parentCommentId: replyId,
- commentSourceType: CommentSourceType.diet,
- );
- context.read().setReplyId(null);
- setState(() {
- textEditingController.clear();
- replyId = null;
- });
- FocusManager.instance.primaryFocus?.unfocus();
- }
- },
- child: Container(
- decoration: BoxDecoration(
- color: textEditingController.text.length > 2 ? appTheme.blueA400 : Colors.grey,
- shape: BoxShape.circle,
- ),
- height: 32,
- width: 32,
- child: Icon(
- Icons.send_rounded,
- size: 18,
- color: Colors.white,
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- appBar: AppBar(
- actions: [
- InkWell(
- borderRadius: BorderRadius.circular(16),
- onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: widget.diet.author),
- child: Ink(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- CircleAvatar(
- maxRadius: 16,
- minRadius: 16,
- backgroundImage: NetworkImage(widget.diet.author.authorProfileIcon),
- ),
- SizedBox(height: 4),
- Text(
- widget.diet.author.authorName,
- style: TextStyle(
- color: Colors.black,
- fontSize: 14,
- fontWeight: FontWeight.w400,
- height: 1,
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- body: SingleChildScrollView(
- padding: EdgeInsets.only(bottom: 160),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text(
- widget.diet.name,
- style: TextStyle(
- color: Colors.black,
- fontSize: 24,
- fontWeight: FontWeight.w500,
- height: 1,
- ),
- ),
- ),
- SizedBox(height: 12),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Row(
- children: [
- Icon(Icons.timer_outlined),
- SizedBox(width: 4),
- Text(
- widget.diet.duration.toString() + ' дней',
- style: TextStyle(
- color: Colors.black,
- fontSize: 12,
- fontWeight: FontWeight.w400,
- height: 1,
- ),
- ),
- ],
- ),
- ),
- if (widget.diet.details.isNotEmpty)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8),
- child: Html(
- data: widget.diet.details,
- style: {
- "*": Style(
- color: Colors.black87,
- ),
- },
- ),
- ),
- SizedBox(height: 12),
- CachedNetworkImage(
- imageUrl: baseUrl + '/storage/' + widget.diet.image,
- ),
- SizedBox(height: 12),
- ListView.separated(
- padding: EdgeInsets.all(16),
- physics: NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- itemBuilder: (context, index) {
- return _dayOfPlan(widget.diet.dailySchedule[index], index);
- },
- separatorBuilder: (context, index) => Padding(
- padding: const EdgeInsets.symmetric(vertical: 16),
- child: Container(
- height: 0.4,
- decoration: BoxDecoration(
- color: Colors.grey,
- borderRadius: BorderRadius.circular(16),
- ),
- ),
- ),
- itemCount: widget.diet.dailySchedule.length,
- ),
- SizedBox(height: 32),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Container(
- height: 0.4,
- decoration: BoxDecoration(
- color: Colors.grey,
- borderRadius: BorderRadius.circular(16),
- ),
- ),
- ),
- SizedBox(height: 8),
- Container(
- padding: EdgeInsets.symmetric(horizontal: 16),
- alignment: Alignment.centerLeft,
- child: Text(
- widget.diet.totalComments.toString() + ' КОММЕНТАРИЕВ',
- style: TextStyle(
- fontSize: 12,
- color: appTheme.gray500,
- fontWeight: FontWeight.w400,
- ),
- ),
- ),
- SizedBox(height: 16),
- if (state is CommentsStateLoading)
- Padding(
- padding: const EdgeInsets.all(32),
- child: SizedBox(
- height: 32,
- width: 32,
- child: RiveAnimation.asset(
- 'assets/rive_animations/loading.riv',
- ),
- ),
- ),
- if (state is CommentsStateLoaded)
- CommentsWidget(
- id: widget.diet.id.toString(),
- commentSourceType: CommentSourceType.diet,
- comment: state.comment,
- replyId: replyId,
- ),
- ],
- ),
- ),
- );
- },
+ );
+ },
+ );
+ },
+ ),
),
);
}
- _dayOfPlan(List> day, int dayNumber) {
+ _dayOfPlan(List> day, int dayNumber, S s) {
_choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
@@ -381,73 +552,91 @@ class _AboutDietScreenState extends State {
);
}
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'День ' + (dayNumber + 1).toString(),
- style: TextStyle(
- color: Colors.black,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- height: 1,
- ),
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: MainBackgroundBouble(
+ padding: EdgeInsets.only(
+ top: 16,
),
- SizedBox(height: 4),
- Text(
- 'Приём пищи',
- style: TextStyle(
- color: Colors.black,
- fontSize: 14,
- fontWeight: FontWeight.w400,
- height: 1,
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ child: Column(
children: [
- _choiceChipWidget(0),
- _choiceChipWidget(1),
- _choiceChipWidget(2),
- _choiceChipWidget(3),
- _choiceChipWidget(4),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ s.day + ' ' + (dayNumber + 1).toString(),
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 20,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ child: CustomDivider(),
+ ),
+ Text(
+ s.meal,
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ _choiceChipWidget(0),
+ _choiceChipWidget(1),
+ _choiceChipWidget(2),
+ _choiceChipWidget(3),
+ _choiceChipWidget(4),
+ ],
+ ),
+ SizedBox(height: 8),
+ ListView.builder(
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s),
+ itemCount: day[widget.selectedIndexes[dayNumber]].length,
+ ),
+ SizedBox(height: 8),
+ ],
+ ),
+ ),
+ CpfcContainer(calories: 0, protein: 0, fat: 0, carbohydrates: 0),
],
),
- SizedBox(height: 8),
- ListView.separated(
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index]),
- separatorBuilder: (context, index) => SizedBox(height: 4),
- itemCount: day[widget.selectedIndexes[dayNumber]].length,
- ),
- ],
+ ),
);
}
- _daily(DailySchedule daily) {
- return Row(
- children: [
- Text(
- '• ' + daily.name,
- style: TextStyle(
- color: Colors.black,
- fontSize: 14,
- fontWeight: FontWeight.w400,
- height: 1.2,
+ _daily(DailySchedule daily, S s) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 12),
+ child: Row(
+ children: [
+ Text(
+ daily.name,
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
),
- ),
- Spacer(),
- Text(
- daily.quantity.toString() + ' ' + daily.portion.name,
- style: TextStyle(
- color: Colors.black,
- fontSize: 14,
- fontWeight: FontWeight.w400,
- height: 1.2,
+ Spacer(),
+ Text(
+ daily.quantity.toString() + ' ' + getLocalizedMeasurementUnits(s, daily.portion),
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ ),
),
- ),
- ],
+ ],
+ ),
);
}
}
diff --git a/lib/features/diets/presentation/widgets/author_card.dart b/lib/features/diets/presentation/widgets/author_card.dart
new file mode 100644
index 00000000..d566dad5
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/author_card.dart
@@ -0,0 +1,107 @@
+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/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+
+class AuthorCard extends StatelessWidget {
+ const AuthorCard({super.key, required this.author});
+ final Author author;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return Container(
+ width: MediaQuery.sizeOf(context).width,
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.cardBackground),
+ child: Stack(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 16, top: 16, bottom: 24),
+ child: Column(
+ children: [
+ ListTile(
+ contentPadding: EdgeInsets.zero,
+ leading: Container(
+ width: 64,
+ height: 64,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ image: DecorationImage(
+ image: NetworkImage(author.authorProfileIcon),
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ subtitle: Text(
+ author.authorName,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w600,
+ color: appTheme.blueGray800,
+ ),
+ ),
+ title: Text(
+ s.diet_author,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w600,
+ color: Color(0xFF6D6F71),
+ ),
+ ),
+ ),
+ Row(
+ children: [
+ Text(
+ 'Конслуьтант',
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(width: 10),
+ InfoBouble(radius: 12, backgroundColor: appTheme.whiteA700, children: [
+ Text(
+ '128-й в рейтинге',
+ style: TextStyle(
+ color: appTheme.blueA400,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ right: 8,
+ top: 8,
+ child: GestureDetector(
+ onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: author),
+ child: Container(
+ width: 28,
+ height: 28,
+ decoration: BoxDecoration(
+ color: appTheme.whiteA700,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Center(
+ child: SvgPicture.asset(
+ 'assets/icons/arrow.svg',
+ width: 12,
+ height: 12,
+ fit: BoxFit.none,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/calendar_widget.dart b/lib/features/diets/presentation/widgets/calendar_widget.dart
new file mode 100644
index 00000000..e2353b28
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/calendar_widget.dart
@@ -0,0 +1,90 @@
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:table_calendar/table_calendar.dart';
+
+class CalendarWidget extends StatefulWidget {
+ const CalendarWidget({super.key});
+
+ @override
+ State createState() => _CalendarWidgetState();
+}
+
+class _CalendarWidgetState extends State {
+ CalendarFormat _calendarFormat = CalendarFormat.month;
+ DateTime _focusedDay = DateTime.now();
+ DateTime? _selectedDay = DateTime.now();
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(top: 5),
+ child: TableCalendar(
+ firstDay: DateTime.utc(2010, 10, 16),
+ lastDay: DateTime.utc(2030, 3, 14),
+ locale: 'ru_RU',
+ startingDayOfWeek: StartingDayOfWeek.monday,
+ focusedDay: _focusedDay,
+ rangeStartDay: _selectedDay,
+ rangeEndDay: _selectedDay?.add(const Duration(days: 6)),
+ calendarFormat: _calendarFormat,
+ headerStyle: HeaderStyle(
+ titleCentered: true,
+ formatButtonVisible: false,
+ leftChevronVisible: false,
+ rightChevronVisible: false,
+ ),
+ rowHeight: 40,
+ selectedDayPredicate: (day) {
+ return isSameDay(_selectedDay, day);
+ },
+ onDaySelected: (selectedDay, focusedDay) {
+ if (!isSameDay(_selectedDay, selectedDay)) {
+ setState(() {
+ _selectedDay = selectedDay;
+ _focusedDay = focusedDay;
+ });
+ }
+ },
+ onFormatChanged: (format) {
+ if (_calendarFormat != format) {
+ setState(() {
+ _calendarFormat = format;
+ });
+ }
+ },
+ onPageChanged: (focusedDay) {
+ _focusedDay = focusedDay;
+ },
+ calendarStyle: CalendarStyle(
+ weekendDecoration: BoxDecoration(
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ defaultDecoration: BoxDecoration(
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ selectedDecoration: BoxDecoration(
+ color: appTheme.blueA400,
+ shape: BoxShape.circle,
+ ),
+ todayTextStyle: TextStyle(
+ color: Colors.black,
+ ),
+ todayDecoration: BoxDecoration(
+ border: Border.all(
+ color: appTheme.blueA400,
+ width: 2,
+ ),
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/cpfc_container.dart b/lib/features/diets/presentation/widgets/cpfc_container.dart
new file mode 100644
index 00000000..e73cf57c
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/cpfc_container.dart
@@ -0,0 +1,46 @@
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+
+class CpfcContainer extends StatelessWidget {
+ const CpfcContainer({super.key, required this.calories, required this.protein, required this.fat, required this.carbohydrates});
+ final double calories;
+ final double protein;
+ final double fat;
+ final double carbohydrates;
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return Container(
+ color: appTheme.cardBackground,
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Column(
+ children: [
+ Text('$calories', style: TextStyle(color: appTheme.blueGray800, fontSize: 16, fontWeight: FontWeight.w600)),
+ Text(s.calories, style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500)),
+ ],
+ ),
+ Column(
+ children: [
+ Text('$protein', style: TextStyle(color: appTheme.blueGray800, fontSize: 16, fontWeight: FontWeight.w600)),
+ Text(s.proteins, style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500)),
+ ],
+ ),
+ Column(
+ children: [
+ Text('$fat', style: TextStyle(color: appTheme.blueGray800, fontSize: 16, fontWeight: FontWeight.w600)),
+ Text(s.fats, style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500)),
+ ],
+ ),
+ Column(
+ children: [
+ Text('$carbohydrates', style: TextStyle(color: appTheme.blueGray800, fontSize: 16, fontWeight: FontWeight.w600)),
+ Text(s.carbohydrates, style: TextStyle(color: appTheme.blueGray800, fontSize: 12, fontWeight: FontWeight.w500)),
+ ],
+ ),
+ ],),
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/expandable_text.dart b/lib/features/diets/presentation/widgets/expandable_text.dart
new file mode 100644
index 00000000..a035c287
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/expandable_text.dart
@@ -0,0 +1,87 @@
+import 'dart:math' as math;
+
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:flutter_svg/svg.dart';
+
+class ExpandableText extends StatefulWidget {
+ final String text;
+ final int maxLines;
+
+ ExpandableText({required this.text, this.maxLines = 3});
+
+ @override
+ _ExpandableTextState createState() => _ExpandableTextState();
+}
+
+class _ExpandableTextState extends State {
+ bool isExpanded = false;
+
+ void toggleExpansion() {
+ setState(() {
+ isExpanded = !isExpanded;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Stack(
+ children: [
+ Html(
+ data: widget.text,
+ style: {
+ "*": Style(
+ color: Colors.black87,
+ maxLines: isExpanded ? null : widget.maxLines,
+ textOverflow: TextOverflow.clip,
+ ),
+ },
+ ),
+ if (!isExpanded)
+ Positioned.fill(
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ height: 35,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Colors.white.withOpacity(0.0),
+ Colors.white.withOpacity(0.6),
+ Colors.white,
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: GestureDetector(
+ onTap: toggleExpansion,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ isExpanded ? s.hide : s.expand,
+ style: TextStyle(color: appTheme.blueA400, fontSize: 16, fontWeight: FontWeight.w500),
+ ),
+ Transform.rotate(angle: isExpanded ? math.pi : 0, child: SvgPicture.asset('assets/icons/arrow_down.svg', width: 12, height: 12)),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/info_bouble.dart b/lib/features/diets/presentation/widgets/info_bouble.dart
new file mode 100644
index 00000000..870859dd
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/info_bouble.dart
@@ -0,0 +1,20 @@
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+
+class InfoBouble extends StatelessWidget {
+ const InfoBouble({super.key, required this.children, this.backgroundColor, this.radius = 100});
+ final List children;
+ final Color? backgroundColor;
+ final double radius;
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
+ decoration: BoxDecoration(
+ color: backgroundColor ?? appTheme.gray50,
+ borderRadius: BorderRadius.circular(radius),
+ ),
+ child: Row(mainAxisSize: MainAxisSize.min, children: children),
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/main_background_bouble.dart b/lib/features/diets/presentation/widgets/main_background_bouble.dart
new file mode 100644
index 00000000..8b94131e
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/main_background_bouble.dart
@@ -0,0 +1,38 @@
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+
+class MainBackgroundBouble extends StatelessWidget {
+ const MainBackgroundBouble(
+ {super.key, required this.child, this.width, this.padding, this.enableShadow = false,});
+ final double? width;
+ final Widget child;
+ final bool enableShadow;
+ final EdgeInsetsGeometry? padding;
+ @override
+ Widget build(BuildContext context) {
+ return DecoratedBox(
+ decoration: BoxDecoration(
+ boxShadow: enableShadow
+ ? [
+ BoxShadow(
+ color: Color(0x00000000).withOpacity(0.2),
+ spreadRadius: 0,
+ blurRadius: 32,
+ offset: Offset(0, 0),
+ ),
+ ]
+ : null,),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(12),
+ child: Container(
+ decoration: BoxDecoration(
+ color: appTheme.whiteA700,
+ ),
+ width: width ?? MediaQuery.sizeOf(context).width,
+ padding: padding,
+ child: child,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/more_options.dart b/lib/features/diets/presentation/widgets/more_options.dart
new file mode 100644
index 00000000..639fe8d2
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/more_options.dart
@@ -0,0 +1,99 @@
+import 'package:eatvio_app/core/const.dart';
+import 'package:eatvio_app/core/utils/toasts.dart';
+import 'package:eatvio_app/core/widgets/custom_divider.dart';
+import 'package:eatvio_app/features/diets/presentation/widgets/main_background_bouble.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:go_router/go_router.dart';
+import 'package:share_plus/share_plus.dart';
+
+class MoreOptions extends StatelessWidget {
+ const MoreOptions({super.key, required this.id});
+ final int id;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return MainBackgroundBouble(
+ enableShadow: true,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ GestureDetector(
+ onTap: () {
+ Clipboard.setData(ClipboardData(text: baseUrl + '/ru/diet/' + id.toString()));
+ showInfoToast(s.link_was_copied);
+ },
+ child: Row(
+ children: [
+ Text(
+ s.copy_link,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: appTheme.blueGray800,
+ ),
+ ),
+ Spacer(),
+ SvgPicture.asset('assets/icons/copy2.svg', width: 16, height: 20),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 12.0),
+ child: CustomDivider(),
+ ),
+ GestureDetector(
+ onTap: () => Share.shareUri(
+ Uri.parse(
+ baseUrl + '/ru/diet/' + id.toString(),
+ ),
+ ),
+ child: Row(
+ children: [
+ Text(
+ s.share,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: appTheme.blueGray800,
+ ),
+ ),
+ Spacer(),
+ SvgPicture.asset('assets/icons/share2.svg', width: 16, height: 20),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 12.0),
+ child: CustomDivider(),
+ ),
+ GestureDetector(
+ onTap: () {
+ context.push('complainingScreen');
+ },
+ child: Row(
+ children: [
+ Text(
+ s.complain,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: appTheme.blueGray800,
+ ),
+ ),
+ Spacer(),
+ SvgPicture.asset('assets/icons/complain.svg', width: 16, height: 20),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ width: 272,);
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart b/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart
new file mode 100644
index 00000000..94e18e89
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart
@@ -0,0 +1,56 @@
+import 'package:eatvio_app/features/diets/presentation/widgets/calendar_widget.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:eatvio_app/widgets/custom_outlined_button.dart';
+import 'package:flutter/material.dart';
+
+class StartDietBottomSheet extends StatelessWidget {
+ const StartDietBottomSheet({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(height: 5),
+ Container(
+ height: 4,
+ width: 40,
+ decoration: BoxDecoration(color: appTheme.lightGrey, borderRadius: BorderRadius.circular(20)),
+ ),
+ SizedBox(height: 22),
+ Text(s.startDiet,
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 20,
+ fontWeight: FontWeight.w600,
+ ),),
+ SizedBox(height: 12),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 25.0),
+ child: Text(s.choose_start_diet_date,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),),
+ ),
+ CalendarWidget(),
+ Padding(
+ padding: EdgeInsets.only(left: 16, right: 16, top: 20, bottom: 25),
+ child: CustomOutlinedButton(
+ height: 52,
+ text: s.continue_,
+ buttonStyle: ButtonStyle(
+ textStyle: WidgetStatePropertyAll(TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ side: WidgetStatePropertyAll(BorderSide.none),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/features/diets/presentation/widgets/type_badges.dart b/lib/features/diets/presentation/widgets/type_badges.dart
new file mode 100644
index 00000000..b72c85d4
--- /dev/null
+++ b/lib/features/diets/presentation/widgets/type_badges.dart
@@ -0,0 +1,231 @@
+import 'package:eatvio_app/features/diets/presentation/widgets/main_background_bouble.dart';
+import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:eatvio_app/theme/theme_helper.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_portal/flutter_portal.dart';
+import 'package:flutter_svg/svg.dart';
+
+class TypeBadges extends StatefulWidget {
+ const TypeBadges({super.key, required this.type});
+
+ final DietType type;
+
+ @override
+ State createState() => _TypeBadgesState();
+}
+
+class _TypeBadgesState extends State {
+ final List overlaysVisibilityState = [false, false, false, false];
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ final List<(String, Widget)> badgeSource = [];
+ if (widget.type.alko == true) {
+ badgeSource.add((
+ 'assets/icons/cocktail.svg',
+ _InfoOverlayEntry(
+ onClose: closeOverlay,
+ assetPath: 'assets/icons/cocktail.svg',
+ title: s.alko_type,
+ description: s.alko_type_desc,
+ )
+ ),);
+ }
+ if (widget.type.veg == true) {
+ badgeSource.add((
+ 'assets/icons/broccoli.svg',
+ _InfoOverlayEntry(
+ onClose: closeOverlay,
+ assetPath: 'assets/icons/broccoli.svg',
+ title: s.vegan_type,
+ description: s.vegan_type_desc,
+ )
+ ),);
+ }
+ if (widget.type.corb == true) {
+ badgeSource.add((
+ 'assets/icons/bread.svg',
+ _InfoOverlayEntry(
+ onClose: closeOverlay,
+ assetPath: 'assets/icons/bread.svg',
+ title: s.corb_type,
+ description: s.corb_type_desc,
+ )
+ ),);
+ }
+ if (widget.type.meat == true) {
+ badgeSource.add((
+ 'assets/icons/meat2.svg',
+ _InfoOverlayEntry(
+ onClose: closeOverlay,
+ assetPath: 'assets/icons/meat2.svg',
+ title: s.meat_type,
+ description: s.meat_type_desc,
+ )
+ ),);
+ }
+ return Row(
+ children: List.generate(
+ badgeSource.length,
+ (index) => _Barrier(
+ visible: overlaysVisibilityState[index],
+ onClose: closeOverlay,
+ child: PortalTarget(
+ visible: overlaysVisibilityState[index],
+ anchor: Aligned(
+ shiftToWithinBound: AxisFlag(x: true, y: true),
+ follower: Alignment.topRight,
+ target: Alignment.bottomRight,
+ ),
+ portalFollower: badgeSource[index].$2,
+ child: GestureDetector(
+ onTap: () {
+ for (int stateIndex = 0; stateIndex < overlaysVisibilityState.length; stateIndex++) {
+ if (index != stateIndex) {
+ setState(() {
+ overlaysVisibilityState[stateIndex] = false;
+ });
+ } else {
+ setState(() {
+ overlaysVisibilityState[stateIndex] = true;
+ });
+ }
+ }
+ },
+ child: Padding(
+ padding: EdgeInsets.only(right: index != badgeSource.length - 1 ? 8.0 : 0),
+ child: SvgPicture.asset(
+ badgeSource[index].$1,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void closeOverlay() {
+ for (int stateIndex = 0; stateIndex < overlaysVisibilityState.length; stateIndex++) {
+ setState(() {
+ overlaysVisibilityState[stateIndex] = false;
+ });
+ }
+ }
+}
+
+class _InfoOverlayEntry extends StatelessWidget {
+ const _InfoOverlayEntry({
+ required this.title,
+ required this.description,
+ required this.assetPath,
+ required this.onClose,
+ });
+
+ final String title;
+ final String description;
+ final assetPath;
+ final void Function() onClose;
+
+ @override
+ Widget build(BuildContext context) {
+ return MainBackgroundBouble(
+ padding: EdgeInsets.all(8),
+ width: 343,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SvgPicture.asset(
+ assetPath,
+ width: 20,
+ height: 20,
+ ),
+ SizedBox(width: 8),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ title,
+ style: TextStyle(
+ decoration: TextDecoration.none,
+ color: appTheme.blueGray800,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(height: 4),
+ Text(
+ description,
+ softWrap: true,
+ style: TextStyle(
+ decoration: TextDecoration.none,
+ color: appTheme.blueGray800,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],
+ ),
+ ),
+ GestureDetector(
+ onTap: onClose,
+ child: SvgPicture.asset(
+ 'assets/icons/close_button.svg',
+ width: 20,
+ height: 20,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class _Barrier extends StatelessWidget {
+ const _Barrier({
+ Key? key,
+ required this.onClose,
+ required this.visible,
+ required this.child,
+ }) : super(key: key);
+
+ final Widget child;
+ final VoidCallback onClose;
+ final bool visible;
+
+ @override
+ Widget build(BuildContext context) {
+ return PortalTarget(
+ visible: visible,
+ closeDuration: kThemeAnimationDuration,
+ portalFollower: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: onClose,
+ child: TweenAnimationBuilder(
+ duration: kThemeAnimationDuration,
+ tween: ColorTween(
+ begin: Colors.transparent,
+ end: visible ? Colors.black54 : Colors.transparent,
+ ),
+ builder: (context, color, child) {
+ return ColoredBox(color: color);
+ },
+ ),
+ ),
+ child: child,
+ );
+ }
+}
+
+class ColorTween extends Tween {
+ ColorTween({required Color begin, required Color end}) : super(begin: begin, end: end);
+
+ @override
+ Color lerp(double t) => Color.lerp(begin, end, t)!;
+}
diff --git a/lib/features/diets/repositories/diets_repository.dart b/lib/features/diets/repositories/diets_repository.dart
new file mode 100644
index 00000000..3e826995
--- /dev/null
+++ b/lib/features/diets/repositories/diets_repository.dart
@@ -0,0 +1,48 @@
+import 'package:eatvio_app/features/diets/services/diets_service.dart';
+import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+
+class DietsRepository {
+ DietsRepository({required this.dietsService});
+ final DietsService dietsService;
+
+ Future likeDiet(int id) async {
+ try {
+ await dietsService.likeDiet(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future unlikeDiet(int id) async {
+ try {
+ await dietsService.unlikeDiet(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future getDiet(int id) async {
+ try {
+ final diet = await dietsService.getDiet(id: id);
+ return diet;
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future addToFavorite(int id) async {
+ try {
+ await dietsService.addToFavorite(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future removeFromFavorite(int id) async {
+ try {
+ await dietsService.removeFromFavorite(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/diets/services/diets_service.dart b/lib/features/diets/services/diets_service.dart
new file mode 100644
index 00000000..d5236e3d
--- /dev/null
+++ b/lib/features/diets/services/diets_service.dart
@@ -0,0 +1,36 @@
+import 'package:dio/dio.dart';
+import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'diets_service.g.dart';
+
+@RestApi()
+abstract class DietsService {
+ factory DietsService(Dio dio, {String baseUrl}) = _DietsService;
+
+ @POST('/api/diets/{id}/like')
+ Future likeDiet({
+ @Path() required int id,
+ });
+
+ @POST('/api/diets/{id}/add_to_favorite')
+ Future addToFavorite({
+ @Path() required int id,
+ });
+
+ @POST('/api/diets/{id}/remove_from_favorite')
+ Future removeFromFavorite({
+ @Path() required int id,
+ });
+
+ @POST('/api/diets/{id}/unliked')
+ Future unlikeDiet({
+ @Path() required int id,
+ });
+
+ @GET('/api/diets/show/{id}')
+ Future getDiet({
+ @Path() required int id,
+ });
+
+}
diff --git a/lib/features/diets/services/diets_service.g.dart b/lib/features/diets/services/diets_service.g.dart
new file mode 100644
index 00000000..67718920
--- /dev/null
+++ b/lib/features/diets/services/diets_service.g.dart
@@ -0,0 +1,186 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'diets_service.dart';
+
+// **************************************************************************
+// RetrofitGenerator
+// **************************************************************************
+
+// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
+
+class _DietsService implements DietsService {
+ _DietsService(
+ this._dio, {
+ this.baseUrl,
+ this.errorLogger,
+ });
+
+ final Dio _dio;
+
+ String? baseUrl;
+
+ final ParseErrorLogger? errorLogger;
+
+ @override
+ Future likeDiet({required int id}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'POST',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diets/${id}/like',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ await _dio.fetch(_options);
+ }
+
+ @override
+ Future addToFavorite({required int id}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'POST',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diets/${id}/add_to_favorite',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ await _dio.fetch(_options);
+ }
+
+ @override
+ Future removeFromFavorite({required int id}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'POST',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diets/${id}/remove_from_favorite',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ await _dio.fetch(_options);
+ }
+
+ @override
+ Future unlikeDiet({required int id}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'POST',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diets/${id}/unliked',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ await _dio.fetch(_options);
+ }
+
+ @override
+ Future getDiet({required int id}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'GET',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diets/show/${id}',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ final _result = await _dio.fetch