Merge pull request 'feat(deepLinks)' (#518) from misha/eatvio_app:deepLinks into main

Reviewed-on: eatvio/eatvio_app#518
This commit is contained in:
mso7nueh
2025-07-02 12:23:16 +00:00
106 changed files with 1326 additions and 1057 deletions

4
.gitignore vendored
View File

@@ -11,4 +11,6 @@
/android/app/upload-keystore.jks
/android/app/.cxx
*.g.dart
*.freezed.dart
*.freezed.dart
android/build/
ios/XCBuildData/

View File

@@ -26,8 +26,30 @@
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep link intent -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="eatvio.ru" />
<data android:host="www.eatvio.ru" />
<data android:pathPattern="/ru/recipe/.*" />
<data android:pathPattern="/en/recipe/.*" />
<data android:pathPattern="/ru/blog_post/.*" />
<data android:pathPattern="/en/blog_post/.*" />
<data android:pathPattern="/ru/diet/.*" />
<data android:pathPattern="/en/diet/.*" />
<data android:pathPattern="/ru/plan/.*" />
<data android:pathPattern="/en/plan/.*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.

View File

@@ -32,7 +32,7 @@
<true/>
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>This permission is not needed by the app, but it is required by an underlying API. If you see this dialog, contact us.</string>
<string>This permission is not needed by the app, but it is required by an underlying API. If you see this dialog, contact us.</string>
<key>NSCameraUsageDescription</key>
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
<key>NSLocationWhenInUseUsageDescription</key>

View File

@@ -4,5 +4,10 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:eatvio.ru</string>
<string>applinks:www.eatvio.ru</string>
</array>
</dict>
</plist>

View File

@@ -46,7 +46,6 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/presentat
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
@@ -71,6 +70,7 @@ import 'package:eatvio_app/features/user_details/presentation/user_details_scree
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -83,6 +83,30 @@ class AppRouter {
static GoRouter appRouter = GoRouter(
initialLocation: '/update-screen',
navigatorKey: navigatorKey,
redirect: (context, state) {
final segments = state.uri.pathSegments;
if (segments.length >= 3) {
final type = segments[1];
final contentIdentifier = segments[2];
switch (type) {
case 'recipe':
FlutterNativeSplash.remove();
return '/about-recipes-screen?slug=$contentIdentifier';
case 'blog_post':
FlutterNativeSplash.remove();
return '/blog-post-screen?id=$contentIdentifier';
case 'plan':
FlutterNativeSplash.remove();
return '/about-plan-screen?slug=$contentIdentifier';
case 'diet':
FlutterNativeSplash.remove();
return '/about-diet-screen?slug=$contentIdentifier';
}
}
return null;
},
routes: [
GoRoute(
path: '/auth-screen',
@@ -261,8 +285,17 @@ class AppRouter {
path: '/about-plan-screen',
name: 'aboutPlanScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutPlanScreen(
slug: slug,
navigateToComments: false,
index: null,
);
}
final data = state.extra as Map<String, dynamic>;
return AboutPlanScreen(plan: data['plan'] as PlanModel, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
@@ -270,9 +303,18 @@ class AppRouter {
path: '/about-recipes-screen',
name: 'aboutRecipesScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutRecipeScreen(
recipeSlug: slug,
navigateToComments: false,
index: null,
);
}
final data = state.extra as Map<String, dynamic>;
return AboutRecipeScreen(
recipe: data['recipe'] as Recipe,
recipeSlug: data['recipe'] as String,
index: data['index'] as int?,
navigateToComments: data['navigateToComments'] as bool,
);
@@ -295,8 +337,16 @@ class AppRouter {
path: '/about-diet-screen',
name: 'aboutDietScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutDietScreen(
slug: slug,
index: null,
navigateToComments: false,
);
}
final data = state.extra as Map<String, dynamic>;
return AboutDietScreen(diet: data['diet'] as Diet, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
@@ -304,7 +354,20 @@ class AppRouter {
name: 'blogPostCreateScreen',
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
),
GoRoute(path: '/blog-post-screen', name: 'blogPostScreen', builder: (context, state) => BlogPostScreen(blogPost: state.extra as FeedBlogPostModel)),
GoRoute(
path: '/blog-post-screen',
name: 'blogPostScreen',
builder: (context, state) {
final id = state.uri.queryParameters['id'];
final parsedId = int.tryParse(id ?? '');
if (parsedId != null) {
return BlogPostScreen(
id: parsedId,
);
}
return BlogPostScreen(id: state.extra as int);
}),
GoRoute(
path: '/chat-screen',
name: 'chatScreen',

View File

@@ -20,12 +20,12 @@ abstract class CredentialsStorage {
};
}
static saveCredentials(String email, String password) async {
static Future<void> saveCredentials(String email, String password) async {
await const FlutterSecureStorage().write(key: 'email', value: email);
await const FlutterSecureStorage().write(key: 'password', value: password);
}
static removeCredentials() async {
static Future<void> removeCredentials() async {
await const FlutterSecureStorage().delete(key: 'email');
await const FlutterSecureStorage().delete(key: 'password');
}

View File

@@ -11,6 +11,7 @@ import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
@@ -165,6 +166,7 @@ Future<void> init() async {
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
// Repository

View File

@@ -7,162 +7,162 @@ import 'package:flutter/material.dart';
class CustomTextStyles {
// Headline style
static get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
static TextStyle get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
// Title text style
static get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(
static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(
fontWeight: FontWeight.w600,
);
static get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.black900,
);
static get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.black900,
fontSize: 13,
);
static get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.black,
);
static get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.black,
);
static get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.black,
);
static get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(
static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(
color: appTheme.whiteA700,
);
static get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.black900,
fontSize: 12,
);
static get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.black,
fontSize: 12,
);
static get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.black,
fontSize: 12,
);
static get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.black,
fontSize: 12,
);
static get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.blueGray900,
fontSize: 12,
);
static get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(
color: appTheme.gray600,
);
static get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(
static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(
color: theme.colorScheme.primary,
fontSize: 12,
);
static get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(
static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(
color: appTheme.black900,
fontSize: 12,
);
// Label text style
static get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(
static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.gray60003,
);
static get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.whiteA700,
);
// Title text style
static get titleMedium16 => theme.textTheme.titleMedium!.copyWith(
static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith(
fontSize: 16,
);
static get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(
static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(
fontSize: 17,
fontWeight: FontWeight.w900,
);
static get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.black900,
);
static get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(
static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w600,
);
static get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.whiteA700,
fontWeight: FontWeight.w600,
);
static get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.blue800,
fontWeight: FontWeight.w800,
);
static get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.black,
);
static get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(
color: appTheme.black,
fontWeight: FontWeight.w700,
);
static get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(
color: appTheme.black,
fontWeight: FontWeight.w800,
);
static get titleSmallBold => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w700,
);
static get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.greenA70001,
);
static get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(
color: appTheme.greenA70001,
fontWeight: FontWeight.w800,
);
static get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.lightGreen300,
fontWeight: FontWeight.w800,
);
static get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.red200,
fontWeight: FontWeight.w800,
);
static get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith(
color: theme.colorScheme.secondaryContainer,
fontWeight: FontWeight.w800,
);
static get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(
static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(
color: theme.colorScheme.secondaryContainer,
);
static get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(
static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(
color: theme.colorScheme.onPrimary,
);
}

View File

@@ -15,7 +15,7 @@ abstract class TokenStorage {
static Future<String?> getTokenFromStorage() async => await const FlutterSecureStorage().read(key: 'token');
static saveTokenInStorage(String token) async {
static Future<void> saveTokenInStorage(String token) async {
await const FlutterSecureStorage().write(key: 'token', value: token);
}

View File

@@ -56,7 +56,7 @@ class CustomDropDown extends StatelessWidget {
: _buildDropDownWidget(context);
}
_buildDropDownWidget(BuildContext context) {
DropdownButtonFormField<SelectionPopupModel> _buildDropDownWidget(BuildContext context) {
return DropdownButtonFormField<SelectionPopupModel>(
focusNode: focusNode ?? FocusNode(),
icon: const Icon(Icons.keyboard_arrow_down_rounded),
@@ -84,7 +84,7 @@ class CustomDropDown extends StatelessWidget {
);
}
void onDropDownChanged(value) {
void onDropDownChanged(dynamic value) {
onChanged!(value!);
}

View File

@@ -76,7 +76,7 @@ class CustomPopup extends StatelessWidget {
);
}
List<PopupMenuEntry> _buildMenuItems(s, context) {
List<PopupMenuEntry> _buildMenuItems(S s,BuildContext context) {
final entries = <PopupMenuEntry>[];
void addItem(String icon, String text, Function() onTap) {
@@ -110,28 +110,28 @@ class CustomPopup extends StatelessWidget {
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
if (complainedContentType == ComplainedContent.posts) {
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(id!);
if (!result) {
if (!result && context.mounted) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('Пост удален, обновите ленту');
} else if (complainedContentType == ComplainedContent.recipe) {
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
if (!result) {
if (!result && context.mounted) {
showErrorDialog(context, title: 'Не удалось удалить рецепт');
return;
}
showSuccessToast('Рецепт удален, обновите ленту');
} else if (complainedContentType == ComplainedContent.diet) {
final result = await sl<DietsCubit>().deleteDiet(id!);
if (!result) {
if (!result && context.mounted) {
showErrorDialog(context, title: 'Не удалось удалить диету');
return;
}
showSuccessToast('Диета удалена, обновите ленту');
} else if (complainedContentType == ComplainedContent.plan) {
final result = await sl<MyPlansCubit>().deletePlan(slug!);
if (!result) {
if (!result && context.mounted) {
showErrorDialog(context, title: 'Не удалось удалить план');
return;
}

View File

@@ -7,7 +7,7 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
ResetPasswordCubit({required this.resetPasswordRepository}) : super(const ResetPasswordState.empty());
trySendEmailCode(String email) async {
Future<void> trySendEmailCode(String email) async {
emit(const ResetPasswordState.loadingSendEmailCode());
try {
if (await resetPasswordRepository.sendEmailCodeForPasswordChange(email)) {
@@ -20,7 +20,7 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
}
}
tryResetPassword(String email, String code, String password, String passwordConfirmation) async {
Future<void> tryResetPassword(String email, String code, String password, String passwordConfirmation) async {
emit(const ResetPasswordState.loadingResetPassword());
try {
if (await resetPasswordRepository.confirmPasswordChange(code, password, passwordConfirmation, email)) {

View File

@@ -0,0 +1,21 @@
import 'dart:developer';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class BlogPostCubit extends Cubit<BlogPostState> {
final FeedRepository feedRepository;
BlogPostCubit(this.feedRepository) : super(BlogPostState());
Future<void> getBlogPost(int id) async {
try {
emit(state.copyWith(isLoading: true));
final postModel = await feedRepository.getBlogPost(id: id);
emit(state.copyWith(blogPostModel: postModel, isLoading: false));
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(state.copyWith(isLoading: false));
}
}
}

View File

@@ -0,0 +1,17 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'blog_post_state.freezed.dart';
part 'blog_post_state.g.dart';
@freezed
abstract class BlogPostState with _$BlogPostState {
factory BlogPostState({
@Default(null) FeedBlogPostModel? blogPostModel,
@Default(false) bool isLoading,
@Default(null) String? error,
}) = _BlogPostState;
factory BlogPostState.fromJson(Map<String, dynamic> json) => _$BlogPostStateFromJson(json);
}

View File

@@ -5,7 +5,8 @@ import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.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/bloc/feed_cubit/feed_cubit.dart';
@@ -19,9 +20,9 @@ import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart';
class BlogPostScreen extends StatefulWidget {
final FeedBlogPostModel blogPost;
final int id;
const BlogPostScreen({super.key, required this.blogPost});
const BlogPostScreen({super.key, required this.id});
@override
State<BlogPostScreen> createState() => _BlogPostScreenState();
@@ -31,264 +32,294 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
final CommentsCubit commentsCubit = sl<CommentsCubit>();
final textEditingController = TextEditingController();
int? replyId;
late bool isLiked;
late int totalLikes;
bool isLiked = false;
int totalLikes = 0;
@override
void initState() {
isLiked = widget.blogPost.wasLikedByMe;
totalLikes = widget.blogPost.totalLikes;
context.read<FeedCubit>().viewPost(widget.blogPost.id);
context.read<FeedCubit>().viewPost(widget.id);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit..getComments(widget.blogPost.id.toString(), CommentSourceType.blogPost, true),
child: BlocConsumer<CommentsCubit, CommentsState>(
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: const 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.withValues(alpha: 0.1)))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
height: 32,
width: 32,
child: const Icon(Icons.close_rounded, size: 18, color: Colors.white),
),
),
if (replyId != null) const SizedBox(width: 8),
SizedBox(
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
height: 1.2,
),
decoration: InputDecoration(
isDense: false,
// убираем true, чтобы padding учитывался корректно
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
// вертикальный паддинг увеличен
hintText: 'Комментарий',
hintStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.2,
color: Color(0xFFAEAEB2),
),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.blogPost.id.toString(),
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.blogPost,
);
context.read<CommentsCubit>().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: const Icon(Icons.send_rounded, size: 18, color: Colors.white),
),
),
],
),
),
appBar: AppBar(
title: Text(widget.blogPost.title),
actions: [
CustomPopup(
isAuthor: widget.blogPost.author.authorIsMe,
complainedContentType: ComplainedContent.posts,
path: 'blog_post/${(widget.blogPost.id)}',
id: widget.blogPost.id,
feedBlogPostModel: widget.blogPost,
userSlug: widget.blogPost.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 80),
physics: const BouncingScrollPhysics(),
child: Column(
children: [
GestureDetector(
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: widget.blogPost.author),
child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircleAvatar(
radius: 16,
foregroundImage: NetworkImage(
baseUrl + widget.blogPost.author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.blogPost.author.authorName,
style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400, height: 1),
),
const SizedBox(height: 2),
Text(
widget.blogPost.createdAt.format('dd MMMM', 'ru'),
style: TextStyle(color: Colors.grey.shade600, fontSize: 12, fontWeight: FontWeight.w400, height: 1),
),
],
),
],
),
),
),
if (widget.blogPost.description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Html(data: widget.blogPost.description.replaceAll('\n', '<br>'), style: {"*": Style(color: Colors.black87)}),
),
const SizedBox(height: 8),
if (widget.blogPost.image.isNotEmpty)
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.blogPost.image}',
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
_iconButton(
icon: Icons.favorite_border_rounded,
text: totalLikes.toString(),
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
onTap: () async {
setState(() {
isLiked = !isLiked;
isLiked ? totalLikes++ : totalLikes--;
});
isLiked
? await context.read<FeedCubit>().likePost(widget.blogPost.id)
: await context.read<FeedCubit>().unlikedPost(widget.blogPost.id);
sl<FeedCubit>().updateFeedBlogPostList(widget.blogPost, isLiked);
return BlocProvider(
create: (context) => sl<BlogPostCubit>()..getBlogPost(widget.id),
child: BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit..getComments(widget.id.toString(), CommentSourceType.blogPost, true),
child: BlocBuilder<BlogPostCubit, BlogPostState>(
builder: (context, blogPostState) {
return BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState state) {
if (state is CommentsStateLoaded) {
setState(() {
replyId = state.replyId;
});
}
},
builder: (BuildContext context, CommentsState state) {
if (blogPostState.isLoading) {
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
),
body: const Loader());
}
isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false;
totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0;
return Scaffold(
resizeToAvoidBottomInset: true,
bottomSheet: Container(
padding: const 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.withValues(alpha: 0.1)))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
height: 32,
width: 32,
child: const Icon(Icons.close_rounded, size: 18, color: Colors.white),
),
),
if (replyId != null) const SizedBox(width: 8),
SizedBox(
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
height: 1.2,
),
decoration: InputDecoration(
isDense: false,
// убираем true, чтобы padding учитывался корректно
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
// вертикальный паддинг увеличен
hintText: 'Комментарий',
hintStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.2,
color: Color(0xFFAEAEB2),
),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
const SizedBox(width: 8),
_iconButton(
icon: Icons.reply_rounded,
GestureDetector(
onTap: () {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.blogPost.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.id.toString(),
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.blogPost,
);
context.read<CommentsCubit>().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: const Icon(Icons.send_rounded, size: 18, color: Colors.white),
),
),
const Spacer(),
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
const SizedBox(width: 4),
Text(widget.blogPost.totalViews.toString(), style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)),
],
),
),
Container(
width: MediaQuery.of(context).size.width - 32,
height: 0.4,
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16)),
appBar: AppBar(
title: Text(blogPostState.blogPostModel?.title ?? ''),
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
actions: [
CustomPopup(
isAuthor: blogPostState.blogPostModel?.author.authorIsMe ?? false,
complainedContentType: ComplainedContent.posts,
path: 'blog_post/${(widget.id)}',
id: widget.id,
feedBlogPostModel: blogPostState.blogPostModel,
userSlug: blogPostState.blogPostModel?.author.authorSlug ?? '',
),
const SizedBox(width: 16),
],
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${widget.blogPost.totalComments} КОММЕНТАРИЕВ',
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
body: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 80),
physics: const BouncingScrollPhysics(),
child: Column(
children: [
GestureDetector(
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: blogPostState.blogPostModel?.author),
child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircleAvatar(
radius: 16,
foregroundImage: NetworkImage(
baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? ''),
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
blogPostState.blogPostModel?.author.authorName ?? '',
style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400, height: 1),
),
const SizedBox(height: 2),
Text(
(blogPostState.blogPostModel?.createdAt ?? DateTime.now()).format('dd MMMM', 'ru'),
style: TextStyle(color: Colors.grey.shade600, fontSize: 12, fontWeight: FontWeight.w400, height: 1),
),
],
),
],
),
),
),
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Html(data: blogPostState.blogPostModel?.description.replaceAll('\n', '<br>'), style: {"*": Style(color: Colors.black87)}),
),
const SizedBox(height: 8),
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}',
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
_iconButton(
icon: Icons.favorite_border_rounded,
text: totalLikes.toString(),
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
onTap: () async {
setState(() {
isLiked = !isLiked;
isLiked ? totalLikes++ : totalLikes--;
});
isLiked ? await context.read<FeedCubit>().likePost(widget.id) : await context.read<FeedCubit>().unlikedPost(widget.id);
sl<FeedCubit>().updateFeedBlogPostList(blogPostState.blogPostModel, isLiked);
},
),
const SizedBox(width: 8),
_iconButton(
icon: Icons.reply_rounded,
onTap: () {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);
},
),
const Spacer(),
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
const SizedBox(width: 4),
Text(blogPostState.blogPostModel?.totalViews.toString() ?? '',
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)),
],
),
),
Container(
width: MediaQuery.of(context).size.width - 32,
height: 0.4,
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16)),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${blogPostState.blogPostModel?.totalComments} КОММЕНТАРИЕВ',
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
),
),
const SizedBox(height: 16),
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
CommentsWidget(id: widget.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId),
],
),
),
const SizedBox(height: 16),
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
CommentsWidget(id: widget.blogPost.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId),
],
),
),
);
},
);
},
);
},
),
),
);
}
_iconButton({required IconData icon, String? text, Function()? onTap, Color? iconColor, Color? backgroundColor, Color? textColor}) {
InkWell _iconButton({required IconData icon, String? text, Function()? onTap, Color? iconColor, Color? backgroundColor, Color? textColor}) {
return InkWell(
onTap: onTap ?? () {},
borderRadius: BorderRadius.circular(16),

View File

@@ -80,7 +80,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
);
}
_showImagePicker() {
void _showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
@@ -145,7 +145,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
);
}
_buildBlogPostCreate(BuildContext context) {
Scaffold _buildBlogPostCreate(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(),
@@ -253,7 +253,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
);
}
_buildAddPhoto() {
GestureDetector _buildAddPhoto() {
return GestureDetector(
onTap: () {
_showImagePicker();

View File

@@ -6,7 +6,7 @@ class NewMessagesCubit extends Cubit<NewMessagesState> {
NewMessagesCubit() : super(const NewMessagesState.empty());
incrementCounter() {
void incrementCounter() {
_messagesCount++;
emit(NewMessagesState.loaded(messagesCount: _messagesCount));
}

View File

@@ -11,7 +11,7 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
required this.profileRepository,
}) : super(const ProfileAvatarState.loading());
getMyProfile() async {
Future<void> getMyProfile() async {
emit(const ProfileAvatarState.loading());
try {
emit(

View File

@@ -7,7 +7,7 @@ class CalendarCubit extends Cubit<CalendarState> {
CalendarCubit({required this.calendarRepository}) : super(const CalendarState.loading());
loadCalendarList() async {
Future<void> loadCalendarList() async {
emit(const CalendarState.loading());
try {
final calendarList = await calendarRepository.getCalendar();

View File

@@ -89,7 +89,7 @@ class CalendarScreen extends StatelessWidget {
);
}
_buildCalendarBody(BuildContext context, CalendarStateLoaded state) {
ListView _buildCalendarBody(BuildContext context, CalendarStateLoaded state) {
return ListView.separated(
padding: const EdgeInsets.all(8),
shrinkWrap: true,

View File

@@ -47,7 +47,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
_timer?.cancel();
}
loadNextPage(String chatId) async {
Future<void> loadNextPage(String chatId) async {
if (currentPage < lastPage && !isLoading) {
isLoading = true;
@@ -89,7 +89,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
}
}
initChat(String chatId, {bool sendInitialMessage = false}) async {
Future<void> initChat(String chatId, {bool sendInitialMessage = false}) async {
try {
final chatRoom = await chatRepository.getChatRoom(chatId);
ChosenPlanModel? chosenPlan;

View File

@@ -69,7 +69,7 @@ class _ChatScreenState extends State<ChatScreen> {
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
}
_onScroll() {
void _onScroll() {
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
chatRoomCubit.loadNextPage(widget.chatId);
}
@@ -102,7 +102,7 @@ class _ChatScreenState extends State<ChatScreen> {
);
}
_buildChat(BuildContext context, ChatRoomStateLoaded state) {
Scaffold _buildChat(BuildContext context, ChatRoomStateLoaded state) {
final chosenPlan = state.chatRoom.chosenPlan;
final amIConsultant = chosenPlan?.consultantId == state.chatRoom.users.sender.id;
final showUserDetailsButton = [
@@ -560,7 +560,7 @@ class _ChatScreenState extends State<ChatScreen> {
return 0;
}
showImagePicker(String slug) {
void showImagePicker(String slug) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
@@ -661,7 +661,7 @@ class _ChatScreenState extends State<ChatScreen> {
);
}
_messageDate(DateTime date) {
Center _messageDate(DateTime date) {
return Center(
child: Padding(
padding: const EdgeInsets.only(top: 32),

View File

@@ -83,7 +83,7 @@ class _ChatsScreenState extends State<ChatsScreen> {
);
}
_buildChatList(BuildContext context, ChatsState state) {
NotificationListener<ScrollNotification> _buildChatList(BuildContext context, ChatsState state) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
loadNewContent(notification, context.read<ChatsCubit>().loadMoreChatRooms);

View File

@@ -167,7 +167,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
);
}
showImagePicker(String slug) {
void showImagePicker(String slug) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(

View File

@@ -78,7 +78,7 @@ class ChatSearchBarWidget extends StatelessWidget {
return searchViewWidget(context);
}
searchViewWidget(BuildContext context) => SizedBox(
SizedBox searchViewWidget(BuildContext context) => SizedBox(
width: width ?? MediaQuery.of(context).size.width,
child: TextFormField(
textCapitalization: TextCapitalization.sentences,

View File

@@ -37,7 +37,7 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
}
}
getMyRequestStatus(String slug) async {
Future<void> getMyRequestStatus(String slug) async {
_update = true;
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
@@ -81,11 +81,11 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
);
}
disableLoading() {
void disableLoading() {
emit(state.copyWith(loading: false));
}
updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
emit(
state.copyWith(
consultant: consultant,
@@ -95,7 +95,7 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
);
}
registerConsultant() async {
Future<void> registerConsultant() async {
if (state.consultant.photos != null) {
updateConsultantProfileModel(
state.consultant.copyWith(

View File

@@ -76,7 +76,7 @@ class _AdditionalSettingsScreenState extends State<AdditionalSettingsScreen> {
);
}
_tile({
ListTile _tile({
required String title,
required Function() onTap,
required bool value,
@@ -102,7 +102,7 @@ class _AdditionalSettingsScreenState extends State<AdditionalSettingsScreen> {
);
}
_divider(BuildContext context) {
Center _divider(BuildContext context) {
return Center(
child: Container(
height: 0.4,

View File

@@ -35,7 +35,7 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
);
}
_startRegistrationButton() {
InkWell _startRegistrationButton() {
return InkWell(
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'),
borderRadius: BorderRadius.circular(20),

View File

@@ -82,7 +82,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
);
}
_divider(BuildContext context) {
Container _divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
@@ -93,7 +93,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
);
}
_diplomaInfo({
Column _diplomaInfo({
required DiplomaEntity diploma,
}) {
return Column(
@@ -159,7 +159,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
);
}
onTapCalendar(BuildContext context, DiplomaEntity diploma) {
Null Function() onTapCalendar(BuildContext context, DiplomaEntity diploma) {
return () {
_showDialog(
CupertinoDatePicker(
@@ -198,7 +198,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
);
}
checkLast() {
void checkLast() {
if (diplomasList.last.textEditingController.text.isNotEmpty || diplomasList.last.date != null) {
setState(() {
diplomasList.add(
@@ -219,7 +219,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
}
}
enableDeleteButton(DiplomaEntity diploma) {
void enableDeleteButton(DiplomaEntity diploma) {
if (diploma.textEditingController.text.isNotEmpty || diploma.date != null) {
setState(() {
diplomasList[diplomasList.indexOf(diploma)].enableDeleteButton = true;
@@ -228,7 +228,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
_saveState();
}
_saveState() {
void _saveState() {
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
certificates: diplomasList.map((e) => CertificatesModel(title: e.textEditingController.text, date: e.date.toString())).toList().sublist(

View File

@@ -80,7 +80,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
);
}
_divider(BuildContext context) {
Container _divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,

View File

@@ -148,7 +148,7 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
);
}
_saveState() {
void _saveState() {
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
photos: imagesPaths,

View File

@@ -104,7 +104,7 @@ class _RegistrationAsScreenState extends State<RegistrationAsScreen> {
);
}
_divider(BuildContext context) {
Container _divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,

View File

@@ -158,7 +158,7 @@ class _SocialNetworksScreenState extends State<SocialNetworksScreen> {
);
}
setLinksState({
void setLinksState({
String? tgLink,
String? vkLink,
String? personalSiteLink,

View File

@@ -354,7 +354,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
);
}
_legendTitle(String text, Color color) {
Row _legendTitle(String text, Color color) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@@ -348,7 +348,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
);
}
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
Padding _dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
@@ -435,7 +435,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
);
}
_daily(DailySchedule? daily, S s) {
SingleChildRenderObjectWidget _daily(DailySchedule? daily, S s) {
if (daily == null) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: 12),

View File

@@ -289,7 +289,7 @@ class _DayWidgetState extends State<DayWidget> {
);
}
_product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) {
Material _product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) {
return Material(
child: ListTile(
onTap: () {
@@ -313,7 +313,7 @@ class _DayWidgetState extends State<DayWidget> {
);
}
_modalBottomSheet({
Future _modalBottomSheet({
required CreateDietCubit createDietCubit,
required int dayIndex,
required int mealIndex,

View File

@@ -32,7 +32,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
),
);
addStep() {
void addStep() {
emit(
state.copyWith.entity(
steps: [
@@ -43,13 +43,13 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
);
}
removeStep(StepEntity step) {
void removeStep(StepEntity step) {
List<StepEntity> steps = [...state.entity.steps];
steps.remove(step);
emit(state.copyWith.entity(steps: steps));
}
addIngredient(i.Ingredient entity) {
void addIngredient(i.Ingredient entity) {
emit(
state.copyWith.entity(
ingredients: [
@@ -60,7 +60,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
);
}
removeIngredient(int index) {
void removeIngredient(int index) {
List<i.Ingredient> ingredients = [...state.entity.ingredients];
ingredients.removeAt(index);
@@ -73,26 +73,26 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
emit(state.copyWith.entity(ingredients: ingredients));
}
replaceIngredientPosition(int oldIndex, int newIndex) {
void replaceIngredientPosition(int oldIndex, int newIndex) {
List<i.Ingredient> ingredients = [...state.entity.ingredients];
final item = ingredients.removeAt(oldIndex);
ingredients.insert(newIndex, item);
emit(state.copyWith.entity(ingredients: ingredients));
}
replaceTitle(String title) {
void replaceTitle(String title) {
emit(state.copyWith.entity(title: title == '' ? null : title));
}
replaceDescription(String description) {
void replaceDescription(String description) {
emit(state.copyWith.entity(description: description == '' ? null : description));
}
changeType(TypeOfRecipe typeOfRecipe) {
void changeType(TypeOfRecipe typeOfRecipe) {
emit(state.copyWith.entity(typeOfRecipe: typeOfRecipe));
}
addTag(String tag) {
void addTag(String tag) {
emit(
state.copyWith.entity(
tags: [
@@ -103,46 +103,46 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
);
}
removeTag(String tag) {
void removeTag(String tag) {
List<String> tags = [...state.entity.tags];
tags.remove(tag);
emit(state.copyWith.entity(tags: tags));
}
replacePortionsCount(int count) {
void replacePortionsCount(int count) {
emit(state.copyWith.entity(countOfPortions: count));
}
replaceEnegryInPortion(double energy) {
void replaceEnegryInPortion(double energy) {
emit(state.copyWith.entity(energyInOnePortion: energy));
}
setTotalCookingMinutes(int minutes) {
void setTotalCookingMinutes(int minutes) {
emit(state.copyWith.entity(totalCookMinutes: minutes));
}
setTotalCookingHours(int hours) {
void setTotalCookingHours(int hours) {
emit(state.copyWith.entity(totalCookHours: hours));
}
setActiveCookingMinutes(int minutes) {
void setActiveCookingMinutes(int minutes) {
emit(state.copyWith.entity(activeCookMinutes: minutes));
}
setActiveCookingHours(int hours) {
void setActiveCookingHours(int hours) {
emit(state.copyWith.entity(activeCookHours: hours));
}
replaceVideoUrl(String videoUrl) {
void replaceVideoUrl(String videoUrl) {
emit(state.copyWith.entity(videoUrl: videoUrl));
}
replaceImage(File? image) {
void replaceImage(File? image) {
emit(state.copyWith.entity(image: image));
}
replaceDescriptionInStep(String description, int index) {
void replaceDescriptionInStep(String description, int index) {
List<StepEntity> steps = [...state.entity.steps];
steps[index] = steps[index].copyWith(description: description);
@@ -150,7 +150,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
emit(state.copyWith.entity(steps: steps));
}
replaceImageInStep(File? image, int index) {
void replaceImageInStep(File? image, int index) {
List<StepEntity> steps = [...state.entity.steps];
steps[index] = steps[index].copyWith(image: image);
@@ -182,7 +182,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
}
}
disableLoading() {
void disableLoading() {
emit(state.copyWith(loading: false));
}

View File

@@ -69,7 +69,7 @@ class _ImageWidgetState extends State<ImageWidget> {
);
}
_showImagePicker() {
void _showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {

View File

@@ -112,7 +112,7 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
);
}
_modalBottomSheet({
Future _modalBottomSheet({
required CreateRecipeCubit createRecipeCubit,
int? portionIndex,
String? productNameControllerText,

View File

@@ -82,7 +82,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
);
}
_showImagePicker() {
void _showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {

View File

@@ -169,7 +169,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
});
showErrorToast('${testCases[index].name} failed');
// Show AlertDialog with error description
if (mounted) {
if (context.mounted) {
showDialog(
context: AppRouter.navigatorKey.currentContext!,
builder: (context) => AlertDialog(
@@ -191,7 +191,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
});
showErrorToast('${testCases[index].name} failed');
// Show AlertDialog with error description
if (mounted) {
if (context.mounted) {
showDialog(
context: AppRouter.navigatorKey.currentContext!,
builder: (context) => AlertDialog(

View File

@@ -64,7 +64,7 @@ abstract class DiaryFutureDietDayModel with _$DiaryFutureDietDayModel {
factory DiaryFutureDietDayModel.fromJson(Map<String, dynamic> json) => _$DiaryFutureDietDayModelFromJson(json);
}
DiaryDietModel? _parseDiet(json) {
DiaryDietModel? _parseDiet(dynamic json) {
if (json == null) return null;
return DiaryDietModel.fromJson(json['diet']);
}
@@ -80,7 +80,7 @@ abstract class DiaryDietModel with _$DiaryDietModel {
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
}
TypeOfDiaryModel _parseDayTypeForFutureDay(json) {
TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) {
return TypeOfDiaryModel(
hasChosenDiet: json['has_chosen_diet'] as bool,
hasChosenPlan: json['has_chosen_plan'] as bool,

View File

@@ -55,7 +55,7 @@ class DiaryCubit extends Cubit<DiaryState> {
return null;
}
setSelectedMeal(int meal) {
void setSelectedMeal(int meal) {
selectedMeal = meal;
final currentState = state;
if (currentState is DiaryStateLoaded) {
@@ -154,7 +154,7 @@ class DiaryCubit extends Cubit<DiaryState> {
return false;
}
fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
Future<void> fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
this.diaryId ??= diaryId;
if (needLoading) {
@@ -215,7 +215,7 @@ class DiaryCubit extends Cubit<DiaryState> {
}
}
finishDiaryDay({
Future<void> finishDiaryDay({
required int diaryId,
required String wakeUp,
required String retire,
@@ -233,7 +233,7 @@ class DiaryCubit extends Cubit<DiaryState> {
return (await diaryRepository.getNutritionData(index));
}
addProductToMeal({
Future<void> addProductToMeal({
required int diaryId,
required int mealId,
required String productName,
@@ -252,7 +252,7 @@ class DiaryCubit extends Cubit<DiaryState> {
await fetchDiary();
}
updateProductInMeal({
Future<void> updateProductInMeal({
required int diaryId,
required int mealId,
required int portionId,
@@ -286,22 +286,22 @@ class DiaryCubit extends Cubit<DiaryState> {
await fetchDiary();
}
markEaten({required int diaryId, required int mealId, required int portionId}) async {
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
restore({required int diaryId, required int mealId, required int portionId}) async {
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
@@ -310,12 +310,12 @@ class DiaryCubit extends Cubit<DiaryState> {
return await glossaryRepository.getGlossaryListByQuery(query);
}
uploadPhotos({required List<File> files, required int diaryId}) async {
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
await fetchDiary();
}
deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
await fetchDiary();
}
@@ -335,32 +335,32 @@ class DiaryCubit extends Cubit<DiaryState> {
return null;
}
fillTime({required int diaryId, required String time}) async {
Future<void> fillTime({required int diaryId, required String time}) async {
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
await fetchDiary();
}
fillWakeup({required int diaryId, required String time}) async {
Future<void> fillWakeup({required int diaryId, required String time}) async {
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
await fetchDiary();
}
fillRetire({required int diaryId, required String time}) async {
Future<void> fillRetire({required int diaryId, required String time}) async {
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
await fetchDiary();
}
fillSatiety({required int diaryId, required int satiety}) async {
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
await fetchDiary();
}
fillMood({required int diaryId, required int mood}) async {
Future<void> fillMood({required int diaryId, required int mood}) async {
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
await fetchDiary();
}
fillComment(String comment) {
void fillComment(String comment) {
this.comment = comment;
}
}

View File

@@ -21,7 +21,7 @@ class _AddProductButtonWidgetState extends State<AddProductButtonWidget> {
diaryCubit = context.read<DiaryCubit>();
}
addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
Future addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,

View File

@@ -134,13 +134,13 @@ class _CalendarWidgetState extends State<CalendarWidget> {
);
}
_nextCalendarFormat() {
void _nextCalendarFormat() {
setState(() {
_calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length];
});
}
_arrowDown() {
GestureDetector _arrowDown() {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragStart: (_) {

View File

@@ -242,7 +242,7 @@ class CaloriesChartWidget extends StatelessWidget {
);
}
_chartData(String title, String value) {
Row _chartData(String title, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@@ -46,7 +46,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
productNameController.text = widget.productName;
}
_alert() {
Padding _alert() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
@@ -137,7 +137,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
);
}
_success() {
Padding _success() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
@@ -189,7 +189,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
);
}
_addNewGlossaryProduct() {
Padding _addNewGlossaryProduct() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(

View File

@@ -34,7 +34,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
diaryCubit = context.read<DiaryCubit>();
}
showImagePicker(DiaryDayModel diaryDay) {
void showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {

View File

@@ -44,7 +44,7 @@ class _ProductWidgetState extends State<ProductWidget> {
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
}
updateProductInMeal(DiaryStateLoaded state, PortionModel portion) {
Future updateProductInMeal(DiaryStateLoaded state, PortionModel portion) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,

View File

@@ -23,7 +23,7 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
diaryCubit = context.read<DiaryCubit>();
}
showTimePicker(int diaryId) {
void showTimePicker(int diaryId) {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,

View File

@@ -26,7 +26,7 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
diaryCubit = context.read<DiaryCubit>();
}
showTimePicker(int diaryId, bool isWakeup) {
void showTimePicker(int diaryId, bool isWakeup) {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -16,10 +17,11 @@ class AboutDietCubit extends Cubit<AboutDietState> {
bool isCurrentDietStarted = false;
bool hasStartedDiet = false;
String? userStartedDietName;
List<int> _selectedIndexes = [];
Future<void> getDiet(int id, {int? index}) async {
Future<void> getDiet(String slug, {int? index}) async {
try {
final diet = await dietsRepository.getDiet(id);
final diet = await dietsRepository.getDietBySlug(slug);
if (index != null) {
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
}
@@ -30,10 +32,10 @@ class AboutDietCubit extends Cubit<AboutDietState> {
}
}
Future<void> initDietPage(int id) async {
Future<void> initDietPage(String slug) async {
try {
emit(const AboutDietState.loading());
final diet = await dietsRepository.getDiet(id);
final diet = await dietsRepository.getDietBySlug(slug);
final chosenDiets = await profileRepository.getChosenDiets();
final lastChosenDiet = chosenDiets.firstOrNull;
if (lastChosenDiet != null && ['inited', 'started'].contains(lastChosenDiet.state)) {
@@ -47,7 +49,13 @@ class AboutDietCubit extends Cubit<AboutDietState> {
}
}
}
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
emit(AboutDietState.loaded(
diet: diet,
isStarted: isCurrentDietStarted,
hasStartedDiet: hasStartedDiet,
selectedIndexes: _selectedIndexes,
));
} catch (e) {
log(e.toString());
emit(AboutDietState.error(message: e.toString()));
@@ -67,7 +75,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndexInFeed != null) {
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
}
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
} else {
final updatedDiet = currentDiet.copyWith(
totalLikes: currentDiet.totalLikes + 1,
@@ -76,7 +84,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndexInFeed != null) {
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
}
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
}
EasyDebounce.debounce(
@@ -91,7 +99,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
dietsRepository
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
}
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
}
},
);
@@ -108,7 +116,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndex != null) {
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
}
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
EasyDebounce.debounce(
'add_or_remove_diet_to_favorites',
@@ -122,7 +130,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndex != null) {
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
}
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
}
},
);
@@ -139,7 +147,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndex != null) {
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
}
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
EasyDebounce.debounce(
'add_or_remove_diet_to_favorites',
@@ -153,7 +161,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
if (dietIndex != null) {
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
}
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
}
},
);
@@ -191,12 +199,31 @@ class AboutDietCubit extends Cubit<AboutDietState> {
void clearStartedDietsState() {
if (state is! DietStateLoaded) return;
final diet = (state as DietStateLoaded).diet;
emit(AboutDietState.loaded(diet: diet));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet));
}
void emitCurrentStartedDietsState() {
if (state is! DietStateLoaded) return;
final diet = (state as DietStateLoaded).diet;
emit(AboutDietState.loaded(diet: diet, isStarted: true));
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet, isStarted: true));
}
Future<Diet?> getDietBySlug(String slug) async {
try {
return await dietsRepository.getDietBySlug(slug);
} catch (e) {
log(e.toString());
}
return null;
}
void setSelectedIndex({required int dayNumber, required int index}) {
final currentState = state;
if (currentState is DietStateLoaded) {
final updatedSelectedIndexes = [...currentState.selectedIndexes];
updatedSelectedIndexes[dayNumber] = index;
_selectedIndexes = updatedSelectedIndexes;
emit(currentState.copyWith(selectedIndexes: _selectedIndexes));
}
}
}

View File

@@ -11,6 +11,7 @@ abstract class AboutDietState with _$AboutDietState {
const factory AboutDietState.loading() = DietStateLoading;
const factory AboutDietState.loaded({
required Diet diet,
@Default([]) List<int> selectedIndexes,
@Default(false) bool isStarted,
@Default(false) bool hasStartedDiet,
}) = DietStateLoaded;

View File

@@ -32,16 +32,14 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
class AboutDietScreen extends StatefulWidget {
final Diet diet;
final String slug;
final int? index;
final bool navigateToComments;
late final List<int> selectedIndexes;
AboutDietScreen({super.key, required this.diet, required this.index, required this.navigateToComments}) {
selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0);
}
const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug});
@override
State<AboutDietScreen> createState() => _AboutDietScreenState();
@@ -55,11 +53,11 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
final textEditingController = TextEditingController();
bool isMoreOptionsVisible = false;
bool _jumped = false;
bool _isFirstLaunch = true;
@override
void initState() {
super.initState();
context.read<FeedCubit>().viewDiet(widget.diet.id);
scrollController.addListener(() {
if (isMoreOptionsVisible) {
setState(() {
@@ -79,15 +77,11 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider(
create: (context) => dietsCubit..initDietPage(widget.diet.id),
create: (context) => dietsCubit..initDietPage(widget.slug),
child: BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit
..getComments(
widget.diet.id.toString(),
CommentSourceType.diet,
true,
),
create: (BuildContext context) => commentsCubit,
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
@@ -100,6 +94,15 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
return BlocBuilder<AboutDietCubit, AboutDietState>(
builder: (context, dietState) {
if (dietState is DietStateLoaded) {
if (_isFirstLaunch) {
context.read<FeedCubit>().viewDiet(dietState.diet.id);
context.read<CommentsCubit>().getComments(
dietState.diet.id.toString(),
CommentSourceType.diet,
true,
);
_isFirstLaunch = false;
}
Future.delayed(const Duration(milliseconds: 500), () {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
@@ -176,9 +179,9 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
isFavorite: dietState.diet.isFavorite,
onTap: () {
if (dietState.diet.isFavorite) {
dietsCubit.removeFromFavorite(widget.diet.id, widget.index);
dietsCubit.removeFromFavorite(dietState.diet.id, widget.index);
} else {
dietsCubit.addToFavorite(widget.diet.id, widget.index);
dietsCubit.addToFavorite(dietState.diet.id, widget.index);
}
},
width: 52,
@@ -269,7 +272,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.diet.id.toString(),
id: dietState.diet.id.toString(),
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.diet,
@@ -280,7 +283,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
replyId = null;
});
Future.delayed(const Duration(milliseconds: 500), () {
dietsCubit.getDiet(widget.diet.id, index: widget.index);
dietsCubit.getDiet(dietState.diet.slug, index: widget.index);
});
FocusManager.instance.primaryFocus?.unfocus();
}
@@ -307,8 +310,17 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
),
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.diet.name,
dietState.diet.name,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
@@ -317,12 +329,12 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
),
actions: [
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.slug}',
isAuthor: dietState.diet.author.authorIsMe,
path: 'diet/${dietState.diet.slug}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
diet: widget.diet,
userSlug: widget.diet.author.authorSlug,
id: dietState.diet.id,
diet: dietState.diet,
userSlug: dietState.diet.author.authorSlug,
),
const SizedBox(width: 16),
],
@@ -341,7 +353,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.diet.image}',
imageUrl: '$baseUrl/storage/${dietState.diet.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
@@ -367,7 +379,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
const SizedBox(width: 12),
GestureDetector(
onTap: () {
dietsCubit.likeDiet(widget.diet.id, widget.index);
dietsCubit.likeDiet(dietState.diet.id, widget.index);
},
child: InfoBouble(
backgroundColor: appTheme.whiteA700,
@@ -413,17 +425,17 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
),
const SizedBox(height: 12),
InfoRow(
diet: widget.diet,
diet: dietState.diet,
type: ProductType.diet,
),
ExpandableText(
text: widget.diet.details,
text: dietState.diet.details,
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.diet.author,
author: dietState.diet.author,
title: s.diet_author,
),
),
@@ -437,7 +449,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Center(
child: Text(
'${s.diet_plan}(${widget.diet.duration.toString()} ${getDayForm(widget.diet.duration, s)})',
'${s.diet_plan}(${dietState.diet.duration.toString()} ${getDayForm(dietState.diet.duration, s)})',
style: TextStyles.headingXLSemibold.copyWith(
color: appTheme.black,
),
@@ -450,9 +462,15 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return _dayOfPlan(widget.diet.dailySchedule[index + 1] ?? {}, index, s);
return _dayOfPlan(
dietState.diet.dailySchedule[index + 1] ?? {},
index,
s,
dietState.selectedIndexes,
dietState.diet,
);
},
itemCount: widget.diet.dailySchedule.length,
itemCount: dietState.diet.dailySchedule.length,
),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -472,7 +490,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
CommentsWidget(
id: widget.diet.id.toString(),
id: dietState.diet.id.toString(),
commentSourceType: CommentSourceType.diet,
comment: state.comment,
replyId: replyId,
@@ -489,53 +507,49 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.diet.name,
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.slug}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
diet: widget.diet,
userSlug: widget.diet.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(
onReload: () => context.read<AboutDietCubit>().getDiet(widget.diet.id, index: widget.index),
onReload: () => context.read<AboutDietCubit>().getDiet(widget.slug, index: widget.index),
),
);
}
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.diet.name,
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.slug}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
diet: widget.diet,
userSlug: widget.diet.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: const Loader(),
);
@@ -547,7 +561,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
);
}
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
Padding _dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s, List<int> selectedIndexes, Diet diet) {
choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
@@ -560,14 +574,12 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
label: Text((index + 1).toString()),
labelStyle: TextStyles.textXSSemibold.copyWith(
color: index == widget.selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600,
color: index == selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600,
),
selected: widget.selectedIndexes[dayNumber] == index,
selected: selectedIndexes[dayNumber] == index,
backgroundColor: appTheme.gray100,
onSelected: (bool selected) {
setState(() {
widget.selectedIndexes[dayNumber] = index;
});
dietsCubit.setSelectedIndex(dayNumber: dayNumber, index: index);
},
);
}
@@ -615,18 +627,18 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s),
itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0,
itemBuilder: (context, index) => _daily(day[selectedIndexes[dayNumber] + 1]?[index + 1], s),
itemCount: day[selectedIndexes[dayNumber] + 1]?.length ?? 0,
),
const SizedBox(height: 8),
],
),
),
CpfcContainer(
calories: widget.diet.dailyNutrition[dayNumber].energy,
proteins: widget.diet.dailyNutrition[dayNumber].proteins,
fats: widget.diet.dailyNutrition[dayNumber].fats,
carbohydrates: widget.diet.dailyNutrition[dayNumber].carbohydrates,
calories: diet.dailyNutrition[dayNumber].energy,
proteins: diet.dailyNutrition[dayNumber].proteins,
fats: diet.dailyNutrition[dayNumber].fats,
carbohydrates: diet.dailyNutrition[dayNumber].carbohydrates,
),
],
),
@@ -634,7 +646,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
);
}
_daily(DailySchedule? daily, S s) {
SingleChildRenderObjectWidget _daily(DailySchedule? daily, S s) {
if (daily == null) return const SizedBox();
return Padding(
padding: const EdgeInsets.only(bottom: 12),

View File

@@ -33,6 +33,15 @@ class DietRepository {
}
}
Future<Diet> getDietBySlug(String slug) async {
try {
final diet = await dietsService.getDietBySlug(slug: slug);
return diet;
} catch (e) {
rethrow;
}
}
Future<Diet> addToFavorite(int id) async {
try {
return await dietsService.addToFavorite(id: id);

View File

@@ -29,6 +29,11 @@ abstract class DietService {
@Path() required int id,
});
@GET('/api/diets/show_by_slug/{slug}')
Future<Diet> getDietBySlug({
@Path() required String slug,
});
@POST('/api/diets/{id}/start')
Future<void> startDiet({
@Path() required int id,

View File

@@ -46,7 +46,7 @@ abstract class PlanModel with _$PlanModel {
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
}
PacketOptions? parseOption(json) {
PacketOptions? parseOption(dynamic json) {
if (json is Map<String, dynamic>) {
return PacketOptions.fromJson(json);
}
@@ -74,7 +74,7 @@ abstract class PacketOptions with _$PacketOptions {
factory PacketOptions.empty() => PacketOptions(items: [], price: double.parse('1200'), short: '', description: '');
}
List<String> _parseItems(json) {
List<String> _parseItems(dynamic json) {
if (json is Map<String, dynamic>) {
return json.values.map((e) => e as String).toList();
}

View File

@@ -53,7 +53,7 @@ abstract class Ingredient with _$Ingredient {
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
}
int _parseProductId(json) {
int _parseProductId(dynamic json) {
if (json is String) {
return int.parse(json);
}

View File

@@ -10,11 +10,11 @@ class CommentsCubit extends Cubit<CommentsState> {
required this.feedRepository,
}) : super(const CommentsState());
setReplyId(int? replyId) {
void setReplyId(int? replyId) {
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
}
getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
Future<void> getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
if (isFirstFetch) emit(const CommentsState.loading());
try {
switch (commentSourceType) {
@@ -36,7 +36,7 @@ class CommentsCubit extends Cubit<CommentsState> {
}
}
sendComment({
Future<void> sendComment({
required String id,
required String commentText,
required CommentSourceType commentSourceType,

View File

@@ -11,7 +11,7 @@ class ConsultantsCubit extends Cubit<ConsultantsState> {
required this.feedRepository,
}) : super(const ConsultantsState());
getConsultants() async {
Future<void> getConsultants() async {
emit(const ConsultantsState.loading());
try {
emit(

View File

@@ -86,7 +86,7 @@ class DietsCubit extends Cubit<DietsState> {
);
}
loadDiets({
Future<void> loadDiets({
String? query,
}) async {
try {

View File

@@ -16,7 +16,8 @@ class FeedCubit extends Cubit<FeedState> {
required this.feedRepository,
}) : super(const FeedState.loading());
updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) {
if (feedBlogPostModel == null) return;
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
feedBlogPostList[index] = feedBlogPostModel.copyWith(
@@ -31,7 +32,7 @@ class FeedCubit extends Cubit<FeedState> {
);
}
loadFeedBlogPostList({
Future<void> loadFeedBlogPostList({
String? query,
}) async {
try {
@@ -79,7 +80,7 @@ class FeedCubit extends Cubit<FeedState> {
}
}
likePost(int id) async {
Future<void> likePost(int id) async {
try {
await feedRepository.likeBlogPost(id: id);
} catch (e, st) {
@@ -87,10 +88,10 @@ class FeedCubit extends Cubit<FeedState> {
}
}
unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
Future<void> unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
viewPost(int id) async => await feedRepository.viewPost(id);
viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
viewDiet(int id) async => await feedRepository.viewDiet(id);
viewPlan(String slug) async => await feedRepository.viewPlan(slug);
Future<void> viewPost(int id) async => await feedRepository.viewPost(id);
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
}

View File

@@ -132,7 +132,7 @@ class PlansCubit extends Cubit<PlansState> {
);
}
loadPlans({
Future<void> loadPlans({
String? query,
}) async {
try {

View File

@@ -40,7 +40,7 @@ class RecipesCubit extends Cubit<RecipesState> {
});
}
loadRecipesList({
Future<void> loadRecipesList({
String? query,
}) async {
try {
@@ -99,7 +99,7 @@ class RecipesCubit extends Cubit<RecipesState> {
}
}
updateTags(RecipeTag tag) {
void updateTags(RecipeTag tag) {
_selectedTags.contains(tag.id) ? _selectedTags.remove(tag.id) : _selectedTags.add(tag.id);
emit(
state.copyWith(

View File

@@ -85,7 +85,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
_onScrollConsultants() {
void _onScrollConsultants() {
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
}

View File

@@ -46,7 +46,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.feedBlogPostList[widget.index],
extra: state.feedBlogPostList[widget.index].id,
),
child: Material(
borderRadius: BorderRadius.circular(12),
@@ -239,7 +239,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
text: state.feedBlogPostList[widget.index].totalComments.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.feedBlogPostList[widget.index],
extra: state.feedBlogPostList[widget.index].id,
),
),
const SizedBox(width: 8),

View File

@@ -53,7 +53,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
);
}
_comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
Container _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(

View File

@@ -121,7 +121,7 @@ class ConsultantWidget extends StatelessWidget {
);
}
_badges(List<BadgeModel> badges, BuildContext context) {
SingleChildScrollView _badges(List<BadgeModel> badges, BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(

View File

@@ -39,7 +39,7 @@ class _DietsWidgetState extends State<DietsWidget> {
onTap: () => GoRouter.of(context).pushNamed(
'aboutDietScreen',
extra: {
'diet': widget.diet,
'slug': widget.diet.slug,
'index': widget.index,
'navigateToComments': false,
},
@@ -178,7 +178,7 @@ class _DietsWidgetState extends State<DietsWidget> {
icon: Icons.chat_bubble_outline,
text: widget.diet.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'index': widget.index, 'navigateToComments': true}),
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'index': widget.index, 'navigateToComments': true}),
),
const SizedBox(
width: 12,

View File

@@ -44,7 +44,7 @@ class _PlanWidgetState extends State<PlanWidget> {
onTap: () => GoRouter.of(context).pushNamed(
'aboutPlanScreen',
extra: {
'plan': widget.plan,
'slug': widget.plan.slug,
'index': widget.index,
'navigateToComments': false,
},
@@ -249,8 +249,10 @@ class _PlanWidgetState extends State<PlanWidget> {
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.plan.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': true}),
onTap: () {
return GoRouter.of(context)
.pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'index': widget.index, 'navigateToComments': true});
},
),
const SizedBox(
width: 12,

View File

@@ -58,7 +58,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
onTap: () => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: {
'recipe': widget.recipe,
'recipe': widget.recipe.slug,
'index': widget.index,
'navigateToComments': false,
},
@@ -213,7 +213,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
onTap: () => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: {
'recipe': widget.recipe,
'recipe': widget.recipe.slug,
'index': widget.index,
'navigateToComments': true,
},
@@ -264,7 +264,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
);
}
_iconButton(String iconPath, [String? text]) {
Container _iconButton(String iconPath, [String? text]) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(

View File

@@ -42,7 +42,7 @@ class _TagsModalWidgetState extends State<TagsModalWidget> {
);
}
_tagChip({
ChoiceChip _tagChip({
required RecipeTag tag,
required bool isSelected,
}) {

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
@@ -27,6 +28,18 @@ class FeedRepository {
}
}
Future<FeedBlogPostModel> getBlogPost({
required int id
}) async {
try {
return await feedService.getBlogPost(
postId: id
);
} catch (e) {
rethrow;
}
}
Future<List<Recipe>> getRecipes({
String? query,
List<int>? tags,

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
@@ -19,6 +20,11 @@ abstract class FeedService {
@Query('query_search') String? query,
});
@GET('/api/blogPosts/show/{postId}')
Future<FeedBlogPostModel> getBlogPost({
@Path('postId') required int postId,
});
@GET('/api/recipes/index')
Future<RecipesPagination> getRecipes({
@Query('page') required int page,

View File

@@ -19,7 +19,7 @@ abstract class ConsultantFinanceModel with _$ConsultantFinanceModel {
factory ConsultantFinanceModel.fromJson(Map<String, dynamic> json) => _$ConsultantFinanceModelFromJson(json);
}
String _parsePayment(json) {
String _parsePayment(dynamic json) {
return ((json as int) / 100).toStringAsFixed(2);
}

View File

@@ -9,7 +9,7 @@ class BlockedUsersCubit extends Cubit<BlockedUsersState> {
required this.blockedUsersRepository,
}) : super(const BlockedUsersState());
getBlockedUsers() async {
Future<void> getBlockedUsers() async {
try {
emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers()));
} catch (e, st) {
@@ -17,12 +17,12 @@ class BlockedUsersCubit extends Cubit<BlockedUsersState> {
}
}
blockUser(String slug) async {
Future<void> blockUser(String slug) async {
await blockedUsersRepository.blockUserBySlug(slug);
await getBlockedUsers();
}
unblockUser(String slug) async {
Future<void> unblockUser(String slug) async {
await blockedUsersRepository.unblockUserBySlug(slug);
await getBlockedUsers();
}

View File

@@ -71,7 +71,7 @@ class MenuScreen extends StatelessWidget {
);
}
_tile({required String title, required String leading, required Function() onTap}) {
ListTile _tile({required String title, required String leading, required Function() onTap}) {
return ListTile(
onTap: onTap,
leading: SvgPicture.asset(leading, height: 24, width: 24, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)),
@@ -80,7 +80,7 @@ class MenuScreen extends StatelessWidget {
);
}
_divider(BuildContext context) {
Container _divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
@@ -88,7 +88,7 @@ class MenuScreen extends StatelessWidget {
);
}
_onTapLogout(BuildContext context) {
Future<Null> Function() _onTapLogout(BuildContext context) {
return () async {
showDialog(
context: context,

View File

@@ -19,7 +19,7 @@ class BlockedUsersRepository {
}
}
blockUserBySlug(String slug) async {
Future<void> blockUserBySlug(String slug) async {
try {
return await blockedUserService.blockUser(
data: {
@@ -32,7 +32,7 @@ class BlockedUsersRepository {
}
}
unblockUserBySlug(String slug) async {
Future<void> unblockUserBySlug(String slug) async {
try {
return await blockedUserService.unblockUser(
data: {

View File

@@ -52,7 +52,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
Stack(
children: [
InkWell(
onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'navigateToComments': false}),
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(

View File

@@ -14,7 +14,7 @@ abstract class CardModel with _$CardModel {
factory CardModel.fromJson(Map<String, dynamic> json) => _$CardModelFromJson(json);
}
bool parseBool(json) {
bool parseBool(dynamic json) {
if (json == 1 || json == true || json == '1') {
return true;
}

View File

@@ -45,7 +45,7 @@ class _MyPlanWidgetState extends State<MyPlanWidget> {
Stack(
children: [
InkWell(
onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'navigateToComments': false}),
onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'navigateToComments': false}),
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(

View File

@@ -106,6 +106,8 @@ class DiaryReminderNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
DiaryReminderNotification({required this.type, required this.isRead});
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {

View File

@@ -66,7 +66,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
Future<void> getNotifications() async {
try {
final notifications = await notificationsRepository.getNotifications(page: 1);
final notifications = await notificationsRepository.getNotifications(page: 2);
_notifications.clear();
_notifications.addAll(notifications ?? []);
emit(

View File

@@ -21,7 +21,7 @@ abstract class ChosenPlanModel with _$ChosenPlanModel {
factory ChosenPlanModel.fromJson(Map<String, dynamic> json) => _$ChosenPlanModelFromJson(json);
}
ChosenPlanModel? parseChosenPlan(json) {
ChosenPlanModel? parseChosenPlan(dynamic json) {
if (json is Map<String, dynamic>) {
return ChosenPlanModel.fromJson(json);
}

View File

@@ -15,7 +15,6 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
@@ -35,8 +34,8 @@ import 'package:go_router/go_router.dart';
import '../../../../core/widgets/option_widget.dart';
class AboutPlanScreen extends StatefulWidget {
const AboutPlanScreen({super.key, required this.plan, required this.navigateToComments, required this.index});
final PlanModel plan;
const AboutPlanScreen({super.key, required this.slug, required this.navigateToComments, required this.index});
final String slug;
final bool navigateToComments;
final int? index;
@override
@@ -53,7 +52,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
@override
void initState() {
super.initState();
context.read<FeedCubit>().viewPlan(widget.plan.slug);
context.read<FeedCubit>().viewPlan(widget.slug);
scrollController.addListener(() {
if (isMoreOptionsVisible) {
setState(() {
@@ -73,464 +72,484 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MultiBlocProvider(
providers: [
BlocProvider<CommentsCubit>(
create: (context) => commentsCubit
..getComments(
widget.plan.slug,
CommentSourceType.plan,
true,
),
),
BlocProvider<AboutPlanCubit>(
create: (context) => sl<AboutPlanCubit>()..getPlanWithLoading(widget.plan.slug),
),
],
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
setState(() {
replyId = commentsState.replyId;
});
}
},
builder: (BuildContext context, CommentsState commentsState) {
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
if (state.isLoading) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
widget.plan.name,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.plan.author.authorIsMe,
path: 'plan/${widget.plan.slug}',
complainedContentType: ComplainedContent.plan,
slug: widget.plan.slug,
userSlug: widget.plan.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: const Loader(),
);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
}
return PopScope(
child: MultiBlocProvider(
providers: [
BlocProvider<CommentsCubit>(
create: (context) => commentsCubit
..getComments(
widget.slug,
CommentSourceType.plan,
true,
),
),
BlocProvider<AboutPlanCubit>(
create: (context) => sl<AboutPlanCubit>()..getPlanWithLoading(widget.slug),
),
],
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
setState(() {
replyId = commentsState.replyId;
});
if (state.error != null) {
}
},
builder: (BuildContext context, CommentsState commentsState) {
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
if (state.isLoading) {
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
centerTitle: true,
title: Text(
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
body: const Loader(),
);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
}
});
if (state.error != null) {
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
centerTitle: true,
title: Text(
state.planModel?.name ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: state.planModel?.author.authorIsMe ?? false,
path: 'plan/${widget.slug}',
complainedContentType: ComplainedContent.plan,
userSlug: state.planModel?.author.authorSlug ?? '',
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutPlanCubit>().getPlan(widget.slug)),
);
}
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
widget.plan.name,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.plan.author.authorIsMe,
path: 'plan/${widget.plan.slug}',
complainedContentType: ComplainedContent.plan,
slug: widget.plan.slug,
userSlug: widget.plan.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutPlanCubit>().getPlan(widget.plan.slug)),
);
}
return Scaffold(
backgroundColor: appTheme.gray100,
resizeToAvoidBottomInset: true,
bottomSheet: Container(
padding: const 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.withValues(alpha: 0.1),
backgroundColor: appTheme.gray100,
resizeToAvoidBottomInset: true,
bottomSheet: Container(
padding: const 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.withValues(alpha: 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: () async {
final (chosenPlan, error) = await context.read<AboutPlanCubit>().chosePlan();
if (error == 'hasPlan') {
showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one);
return;
}
if (chosenPlan == null) {
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: () async {
final (chosenPlan, error) = await context.read<AboutPlanCubit>().chosePlan();
if (error == 'hasPlan') {
showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one);
return;
}
if (chosenPlan == null) {
if (!context.mounted) return;
showErrorDialog(context, title: 'Не удалось заказать план');
return;
}
if (!context.mounted) return;
showErrorDialog(context, title: 'Не удалось заказать план');
return;
}
if (!context.mounted) return;
GoRouter.of(context).pushReplacementNamed(
'chatScreen',
extra: {
'chatId': chosenPlan.chatId,
'sendInitialMessage': true,
},
);
},
label: Text(
s.to_order,
style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
GoRouter.of(context).pushReplacementNamed(
'chatScreen',
extra: {
'chatId': chosenPlan.chatId,
'sendInitialMessage': true,
},
);
},
label: Text(
s.to_order,
style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
),
),
),
),
),
const SizedBox(width: 8),
BookmarkButton(
isFavorite: state.planModel?.isFavorite ?? false,
onTap: () {
final planModel = state.planModel;
if (planModel == null) return;
if (planModel.isFavorite) {
context.read<AboutPlanCubit>().removeFromFavorite(slug: planModel.slug, index: widget.index);
} else {
context.read<AboutPlanCubit>().addToFavorite(slug: planModel.slug, index: widget.index);
}
},
width: 52,
height: 52,
size: 24,
shape: BoxShape.circle,
color: appTheme.gray50,
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
const SizedBox(width: 8),
BookmarkButton(
isFavorite: state.planModel?.isFavorite ?? false,
onTap: () {
final planModel = state.planModel;
if (planModel == null) return;
if (planModel.isFavorite) {
context.read<AboutPlanCubit>().removeFromFavorite(slug: planModel.slug, index: widget.index);
} else {
context.read<AboutPlanCubit>().addToFavorite(slug: planModel.slug, index: widget.index);
}
},
width: 52,
height: 52,
size: 24,
shape: BoxShape.circle,
color: appTheme.gray50,
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: const Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) const SizedBox(width: 8),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
height: 1.2,
),
decoration: InputDecoration(
isDense: false, // убираем true, чтобы padding учитывался корректно
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен
hintText: 'Комментарий',
hintStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.2,
color: Color(0xFFAEAEB2),
),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.plan,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
Future.delayed(const Duration(milliseconds: 500), () {
if (context.mounted) {
context.read<AboutPlanCubit>().getPlan(widget.slug);
}
});
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: const Icon(
Icons.close_rounded,
child: Icon(
Icons.send_rounded,
size: 18,
color: Colors.white,
color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
),
),
),
if (replyId != null) const SizedBox(width: 8),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
height: 1.2,
),
decoration: InputDecoration(
isDense: false, // убираем true, чтобы padding учитывался корректно
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен
hintText: 'Комментарий',
hintStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.2,
color: Color(0xFFAEAEB2),
),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.plan.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.plan,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
Future.delayed(const Duration(milliseconds: 500), () {
if (context.mounted) {
context.read<AboutPlanCubit>().getPlan(widget.plan.slug);
}
});
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: Container(
decoration: BoxDecoration(
color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.send_rounded,
size: 18,
color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
),
),
),
],
),
],
],
),
],
),
),
),
),
appBar: AppBar(
centerTitle: true,
title: Text(
widget.plan.name,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.plan.author.authorIsMe,
path: 'plan/${widget.plan.slug}',
complainedContentType: ComplainedContent.plan,
slug: widget.plan.slug,
userSlug: widget.plan.author.authorSlug,
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.only(bottom: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
MainBackgroundBouble(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.plan.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
),
Positioned(
bottom: 16,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
const SizedBox(width: 8),
Text(
widget.plan.totalViews.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
context.read<AboutPlanCubit>().likePlan(slug: widget.plan.slug, index: widget.index);
},
child: InfoBouble(
title: Text(
state.planModel?.name ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: state.planModel?.author.authorIsMe ?? false,
path: 'plan/${widget.slug}',
complainedContentType: ComplainedContent.plan,
userSlug: state.planModel?.author.authorSlug ?? '',
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.only(bottom: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
MainBackgroundBouble(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${state.planModel?.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
),
Positioned(
bottom: 16,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
width: 14,
height: 12,
colorFilter: state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
),
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
const SizedBox(width: 8),
Text(
state.planModel?.totalLikes.toString() ?? '0',
state.planModel?.totalViews.toString() ?? '',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
),
const SizedBox(width: 12),
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
const SizedBox(width: 8),
Text(
widget.plan.totalComments.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
context.read<AboutPlanCubit>().likePlan(slug: widget.slug, index: widget.index);
},
child: InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
width: 14,
height: 12,
colorFilter:
state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
),
const SizedBox(width: 8),
Text(
state.planModel?.totalLikes.toString() ?? '0',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
],
),
],
),
const SizedBox(width: 12),
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
const SizedBox(width: 8),
Text(
state.planModel?.totalComments.toString() ?? '',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
],
),
),
],
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
s.about_plan,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(height: 12),
InfoRow(plan: state.planModel, type: ProductType.plan),
ExpandableText(
text: state.planModel?.details ?? '',
),
if (state.planModel != null) ...[
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: (state.planModel?.author)!,
title: s.plan_author,
),
),
],
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
s.about_plan,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(height: 12),
InfoRow(plan: state.planModel, type: ProductType.plan),
ExpandableText(
text: widget.plan.details,
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.plan.author,
title: s.plan_author,
),
),
const SizedBox(height: 16),
],
const SizedBox(height: 16),
],
),
),
),
const SizedBox(height: 16),
if (state.planModel?.packetsCount == 3) ...[
OptionsTab(
cost: [
state.planModel?.packet1Options?.price ?? 0,
state.planModel?.packet2Options?.price ?? 0,
state.planModel?.packet3Options?.price ?? 0,
],
const SizedBox(height: 16),
if (state.planModel?.packetsCount == 3) ...[
OptionsTab(
cost: [
state.planModel?.packet1Options?.price ?? 0,
state.planModel?.packet2Options?.price ?? 0,
state.planModel?.packet3Options?.price ?? 0,
],
),
const SizedBox(height: 12),
],
switch (state.chosenOptionIndex) {
1 => OptionWidget(
option: state.planModel?.packet2Options,
),
2 => OptionWidget(
option: state.planModel?.packet3Options,
),
_ => OptionWidget(
option: state.planModel?.packet1Options,
),
},
const SizedBox(
height: 16,
),
const SizedBox(height: 12),
],
switch (state.chosenOptionIndex) {
1 => OptionWidget(
option: state.planModel?.packet2Options,
),
2 => OptionWidget(
option: state.planModel?.packet3Options,
),
_ => OptionWidget(
option: state.planModel?.packet1Options,
),
},
const SizedBox(
height: 16,
),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}',
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray500,
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}',
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray500,
),
),
),
),
const SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
if (commentsState is CommentsStateLoaded)
CommentsWidget(
id: widget.plan.id.toString(),
commentSourceType: CommentSourceType.plan,
comment: commentsState.comment,
replyId: replyId,
),
],
const SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
if (commentsState is CommentsStateLoaded)
CommentsWidget(
id: state.planModel?.id.toString() ?? '',
commentSourceType: CommentSourceType.plan,
comment: commentsState.comment,
replyId: replyId,
),
],
),
),
),
],
],
),
),
),
);
},
);
},
);
},
);
},
),
),
);
}

View File

@@ -26,7 +26,7 @@ abstract class ChosenDietModel with _$ChosenDietModel {
factory ChosenDietModel.fromJson(Map<String, dynamic> json) => _$ChosenDietModelFromJson(json);
}
int? _parseDietId(json) {
int? _parseDietId(dynamic json) {
if (json == null) return null;
return json['id'] as int?;
}

View File

@@ -11,7 +11,7 @@ class PlansCubit extends Cubit<PlansState> {
required this.plansRepository,
}) : super(const PlansState());
getPlans(String slug) async {
Future<void> getPlans(String slug) async {
emit(const PlansState.loading());
try {
emit(PlansState.loaded(plansList: await plansRepository.getPlansBySlug(slug)));

View File

@@ -63,7 +63,7 @@ class ProfileCubit extends Cubit<ProfileState> {
}
}
updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
void updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
List<FeedBlogPostModel> feedBlogPostList = [...(state as ProfileStateLoaded).blogPosts];
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
feedBlogPostList[index] = feedBlogPostModel.copyWith(
@@ -79,7 +79,7 @@ class ProfileCubit extends Cubit<ProfileState> {
);
}
addCircumference({
Future<void> addCircumference({
required int chestCircumference,
required int waistCircumference,
required int hipCircumference,
@@ -96,7 +96,7 @@ class ProfileCubit extends Cubit<ProfileState> {
}
}
addWeight({
Future<void> addWeight({
required double weight,
}) async {
try {
@@ -107,7 +107,7 @@ class ProfileCubit extends Cubit<ProfileState> {
}
}
getChatRoomIdBySlug({
Future<String?> getChatRoomIdBySlug({
required Author author,
}) async {
try {
@@ -126,19 +126,21 @@ class ProfileCubit extends Cubit<ProfileState> {
'chatId': chatIdOrError,
},
);
return chatIdOrError;
}
} catch (e, st) {
log(e.toString(), stackTrace: st);
return '';
}
return null;
}
uploadPhoto(File file) async {
Future<void> uploadPhoto(File file) async {
await profileRepository.uploadPhoto(await ImageCompressUtility.compressFile(file));
return await getMyProfile();
}
deletePhoto() async {
Future<void> deletePhoto() async {
await profileRepository.deletePhoto();
return await getMyProfile();
}

View File

@@ -9,7 +9,7 @@ class SubCubit extends Cubit<SubState> {
required this.profileRepository,
}) : super(const SubState());
getSubscriptions(String slug, [String? query]) async {
Future<void> getSubscriptions(String slug, [String? query]) async {
emit(const SubState.loading());
try {
emit(SubState.loaded(subList: await profileRepository.getSubscriptionsBySlug(slug, query)));
@@ -18,7 +18,7 @@ class SubCubit extends Cubit<SubState> {
}
}
getSubscribers(String slug, [String? query]) async {
Future<void> getSubscribers(String slug, [String? query]) async {
emit(const SubState.loading());
try {
emit(SubState.loaded(subList: await profileRepository.getSubscribersBySlug(slug, query)));
@@ -27,11 +27,11 @@ class SubCubit extends Cubit<SubState> {
}
}
subscribe(String slug) async {
Future<void> subscribe(String slug) async {
return await profileRepository.subscribe(slug);
}
unsubscribe(String slug) async {
Future<void> unsubscribe(String slug) async {
return await profileRepository.unsubscribe(slug);
}
}

View File

@@ -119,7 +119,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_profileScreen(ProfileStateLoaded state) {
Scaffold _profileScreen(ProfileStateLoaded state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(),
@@ -213,7 +213,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_myProfileScreen(ProfileStateLoaded state) {
Scaffold _myProfileScreen(ProfileStateLoaded state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(toolbarHeight: 0),
@@ -306,7 +306,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_activePlans(ProfileStateLoaded state) {
Widget _activePlans(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
}
@@ -365,7 +365,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_bullets(int currentBullet, int bulletCount) {
Container _bullets(int currentBullet, int bulletCount) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
@@ -384,7 +384,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_bullet(bool isActive) {
AnimatedContainer _bullet(bool isActive) {
return AnimatedContainer(
width: isActive ? 10 : 8,
height: isActive ? 10 : 8,
@@ -396,7 +396,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_newBlogPostButton() {
Material _newBlogPostButton() {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
@@ -437,7 +437,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_diplomasAndCertificates(ProfileStateLoaded state) {
SingleChildRenderObjectWidget _diplomasAndCertificates(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant') || state.consultantInfo!.photos.isEmpty) {
return const SizedBox.shrink();
}
@@ -510,7 +510,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_diplomaImage(String url) {
GestureDetector _diplomaImage(String url) {
return GestureDetector(
onTap: () {
context.pushTransparentRoute(
@@ -536,7 +536,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_education(ProfileStateLoaded state) {
SingleChildRenderObjectWidget _education(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant') ||
state.consultantInfo!.educationModel.completedCourses == null ||
(state.consultantInfo!.educationModel.completedCourses?.isEmpty ?? false)) {
@@ -603,7 +603,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_educationBlock(String title, String date) {
SizedBox _educationBlock(String title, String date) {
final parsedDate = DateTime.tryParse(date);
final formattedDate = parsedDate != null ? DateFormat('d MMMM y', 'ru_RU').format(parsedDate) : date;
@@ -643,7 +643,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_socialLinks(ProfileStateLoaded state) {
SingleChildRenderObjectWidget _socialLinks(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
}
@@ -701,7 +701,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_socialLinkButton(Function() onTap, String icon, bool disabled) {
InkWell _socialLinkButton(Function() onTap, String icon, bool disabled) {
return InkWell(
onTap: disabled ? null : onTap,
borderRadius: BorderRadius.circular(20),
@@ -723,7 +723,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_badges(ProfileStateLoaded state) {
Padding _badges(ProfileStateLoaded state) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: InkWell(
@@ -815,7 +815,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_subscriptionsAndSubscribers(ProfileStateLoaded state) {
Padding _subscriptionsAndSubscribers(ProfileStateLoaded state) {
return Padding(
padding: const EdgeInsets.all(4),
child: Row(
@@ -924,7 +924,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_miniAvatars(List<String> urls) {
Row _miniAvatars(List<String> urls) {
int maxLength = urls.length > 2 ? 3 : urls.length;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -944,7 +944,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_blogPosts(ProfileStateLoaded state) {
ListView _blogPosts(ProfileStateLoaded state) {
return ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 16),
shrinkWrap: true,
@@ -958,7 +958,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_additionalInfo(ProfileStateLoaded state) {
Container _additionalInfo(ProfileStateLoaded state) {
String text = state.profileDetails.additionalInfo;
if (state.profileDetails.additionalInfo.length > 250) {
text = state.profileDetails.additionalInfo.substring(0, 250);
@@ -976,7 +976,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_header(ProfileStateLoaded state) {
Padding _header(ProfileStateLoaded state) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
@@ -1212,7 +1212,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_additional(String title, String value) {
Row _additional(String title, String value) {
return Row(
children: [
Text(
@@ -1235,7 +1235,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_messageAndSubscribe(ProfileStateLoaded state) {
Row _messageAndSubscribe(ProfileStateLoaded state) {
return Row(
children: [
const SizedBox(width: 16),
@@ -1286,7 +1286,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_charts() {
Material _charts() {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
@@ -1350,7 +1350,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_choiceIntervalChipWidget(int index) {
ChoiceChip _choiceIntervalChipWidget(int index) {
return ChoiceChip(
selectedColor: Colors.grey.withValues(alpha: 0.1),
pressElevation: 0,
@@ -1376,7 +1376,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_choiceChipWidget(int index) {
ChoiceChip _choiceChipWidget(int index) {
return ChoiceChip(
selectedColor: appTheme.blueA400,
pressElevation: 0,

View File

@@ -79,7 +79,7 @@ class _SubsScreenState extends State<SubsScreen> {
);
}
_buildStates(SubState state) {
StatelessWidget? _buildStates(SubState state) {
if (state is SubStateLoading) {
return const Loader();
}
@@ -94,5 +94,6 @@ class _SubsScreenState extends State<SubsScreen> {
itemCount: state.subList.length,
);
}
return null;
}
}

View File

@@ -47,7 +47,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.blogPosts[widget.index],
extra: state.blogPosts[widget.index].id,
),
child: Ink(
decoration: BoxDecoration(
@@ -191,7 +191,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
isLiked
? await context.read<FeedCubit>().likePost(state.blogPosts[widget.index].id)
: await context.read<FeedCubit>().unlikedPost(state.blogPosts[widget.index].id);
await widget.profileCubit.updateFeedBlogPostList(
widget.profileCubit.updateFeedBlogPostList(
state.blogPosts[widget.index],
isLiked ? false : true,
);
@@ -211,7 +211,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
text: state.blogPosts[widget.index].totalComments.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.blogPosts[widget.index],
extra: state.blogPosts[widget.index].id,
),
),
const SizedBox(width: 8),

View File

@@ -17,7 +17,7 @@ class MyRecipesCubit extends Cubit<MyRecipesState> {
required this.aboutRecipeRepository,
}) : super(const MyRecipesState.loading());
getMyRecipes() async {
Future<void> getMyRecipes() async {
emit(const MyRecipesState.loading());
try {
final myRecipes = await myRecipesRepository.getMyRecipes();
@@ -28,7 +28,7 @@ class MyRecipesCubit extends Cubit<MyRecipesState> {
}
}
setActiveState() async {
Future<void> setActiveState() async {
emit(const MyRecipesState.loading());
try {
final myRecipes = await myRecipesRepository.getMyRecipes();
@@ -39,7 +39,7 @@ class MyRecipesCubit extends Cubit<MyRecipesState> {
}
}
setArchivedState() async {
Future<void> setArchivedState() async {
emit(const MyRecipesState.loading());
try {
final myRecipes = await myRecipesRepository.getMyRecipes();

View File

@@ -16,7 +16,6 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/recipes_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/bloc/feed_cubit/feed_cubit.dart';
@@ -32,14 +31,15 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutRecipeScreen extends StatefulWidget {
final Recipe recipe;
final String recipeSlug;
final int? index;
final bool navigateToComments;
const AboutRecipeScreen({super.key, required this.recipe, required this.index, required this.navigateToComments});
const AboutRecipeScreen({super.key, required this.recipeSlug, required this.index, required this.navigateToComments});
@override
State<AboutRecipeScreen> createState() => _AboutRecipeScreenState();
@@ -57,7 +57,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
@override
void initState() {
super.initState();
context.read<FeedCubit>().viewRecipe(widget.recipe.slug);
context.read<FeedCubit>().viewRecipe(widget.recipeSlug);
scrollController.addListener(() {
if (isMoreOptionsVisible) {
setState(() {
@@ -76,9 +76,9 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider(
create: (context) => recipeCubit..getRecipe(widget.recipe.slug),
create: (context) => recipeCubit..getRecipe(widget.recipeSlug),
child: BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit..getComments(widget.recipe.slug.toString(), CommentSourceType.recipe, true),
create: (BuildContext context) => commentsCubit..getComments(widget.recipeSlug, CommentSourceType.recipe, true),
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
@@ -170,7 +170,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.recipe.slug,
id: widget.recipeSlug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.recipe,
@@ -181,7 +181,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
replyId = null;
});
Future.delayed(const Duration(milliseconds: 500), () {
recipeCubit.getRecipe(widget.recipe.slug, index: widget.index);
recipeCubit.getRecipe(widget.recipeSlug, index: widget.index);
});
FocusManager.instance.primaryFocus?.unfocus();
}
@@ -204,8 +204,17 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
),
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.recipe.title ?? '',
recipeState.recipe.title ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
@@ -214,12 +223,12 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
),
actions: [
CustomPopup(
isAuthor: widget.recipe.author.authorIsMe,
path: 'recipe/${widget.recipe.slug}',
isAuthor: recipeState.recipe.author.authorIsMe,
path: 'recipe/${widget.recipeSlug}',
complainedContentType: ComplainedContent.recipe,
slug: widget.recipe.slug,
recipe: widget.recipe,
userSlug: widget.recipe.author.authorSlug,
slug: widget.recipeSlug,
recipe: recipeState.recipe,
userSlug: recipeState.recipe.author.authorSlug,
),
const SizedBox(width: 16),
],
@@ -238,7 +247,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.recipe.image}',
imageUrl: '$baseUrl/storage/${recipeState.recipe.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
@@ -255,13 +264,13 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
children: [
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
const SizedBox(width: 8),
Text(widget.recipe.viewsCount.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
Text(recipeState.recipe.viewsCount.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
],
),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
recipeCubit.likeRecipe(widget.recipe.slug, widget.index);
recipeCubit.likeRecipe(widget.recipeSlug, widget.index);
},
child: InfoBouble(
backgroundColor: appTheme.whiteA700,
@@ -300,9 +309,9 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
child: BookmarkButton(
onTap: () async {
if (recipeState.recipe.isFavorite) {
await context.read<AboutRecipeCubit>().removeFromFavorite(widget.recipe.slug, widget.index);
await context.read<AboutRecipeCubit>().removeFromFavorite(widget.recipeSlug, widget.index);
} else {
await context.read<AboutRecipeCubit>().addToFavorite(widget.recipe.slug, widget.index);
await context.read<AboutRecipeCubit>().addToFavorite(widget.recipeSlug, widget.index);
}
},
color: appTheme.whiteA700,
@@ -321,14 +330,14 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
child: Text(s.about_recipe, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
InfoRow(recipe: widget.recipe, type: ProductType.recipe),
if (widget.recipe.videoUrl != null && widget.recipe.videoUrl!.isNotEmpty)
InfoRow(recipe: recipeState.recipe, type: ProductType.recipe),
if (recipeState.recipe.videoUrl != null && recipeState.recipe.videoUrl!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 12, top: 12),
child: GestureDetector(
onTap: () async {
try {
final uri = Uri.parse(widget.recipe.videoUrl!);
final uri = Uri.parse(recipeState.recipe.videoUrl!);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
@@ -347,19 +356,19 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
),
),
),
ExpandableText(text: widget.recipe.description),
ExpandableText(text: recipeState.recipe.description),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(author: widget.recipe.author, title: s.recipe_author),
child: AuthorCard(author: recipeState.recipe.author, title: s.recipe_author),
),
const SizedBox(height: 16),
],
),
),
const SizedBox(height: 12),
if (widget.recipe.ingredients?.isNotEmpty == true) IngredientsWidget(recipe: widget.recipe),
if (widget.recipe.steps?.isNotEmpty == true) ...[const SizedBox(height: 12), StepsWidget(recipe: widget.recipe)],
if (recipeState.recipe.ingredients?.isNotEmpty == true) IngredientsWidget(recipe: recipeState.recipe),
if (recipeState.recipe.steps?.isNotEmpty == true) ...[const SizedBox(height: 12), StepsWidget(recipe: recipeState.recipe)],
const SizedBox(height: 8),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -369,14 +378,15 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${recipeState.recipe.commentsCount} ${getCommentForm(widget.recipe.commentsCount, s)}',
'${recipeState.recipe.commentsCount} ${getCommentForm(recipeState.recipe.commentsCount, s)}',
style: TextStyles.textMMedium.copyWith(color: appTheme.gray500),
),
),
const SizedBox(height: 16),
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
CommentsWidget(id: widget.recipe.slug, commentSourceType: CommentSourceType.recipe, comment: state.comment, replyId: replyId),
CommentsWidget(
id: recipeState.recipe.slug, commentSourceType: CommentSourceType.recipe, comment: state.comment, replyId: replyId),
],
),
),
@@ -389,51 +399,47 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.recipe.title ?? '',
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.recipe.author.authorIsMe,
path: 'recipe/${widget.recipe.slug}',
complainedContentType: ComplainedContent.recipe,
slug: widget.recipe.slug,
recipe: widget.recipe,
userSlug: widget.recipe.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutRecipeCubit>().getRecipe(widget.recipe.slug, index: widget.index)),
body: DataErrorWidget(onReload: () => context.read<AboutRecipeCubit>().getRecipe(widget.recipeSlug, index: widget.index)),
);
}
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
widget.recipe.title ?? '',
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: widget.recipe.author.authorIsMe,
path: 'recipe/${widget.recipe.slug}',
complainedContentType: ComplainedContent.recipe,
slug: widget.recipe.slug,
recipe: widget.recipe,
userSlug: widget.recipe.author.authorSlug,
),
const SizedBox(width: 16),
],
),
body: const Loader(),
);

View File

@@ -64,7 +64,7 @@ class _MyRecipeWidgetState extends State<MyRecipeWidget> {
onTap: () async => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: {
'recipe': await context.read<MyRecipesCubit>().getRecipeById(widget.recipe.slug),
'recipe': widget.recipe.slug,
'navigateToComments': false,
},
),
@@ -189,7 +189,7 @@ class _MyRecipeWidgetState extends State<MyRecipeWidget> {
);
}
_iconButton(String iconPath, [String? text]) {
Container _iconButton(String iconPath, [String? text]) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(

View File

@@ -48,7 +48,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
),
);
updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
void updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
Future<void> tryRegistration() async {
if (await registrationRepository.emailIsReachable(state.sheet.email)) {

View File

@@ -9,7 +9,7 @@ class VerifyEmailCubit extends Cubit<VerifyEmailState> {
required this.profileRepository,
}) : super(const VerifyEmailState.loading());
sendEmailCode() async {
Future<void> sendEmailCode() async {
try {
final profile = await profileRepository.getMyProfile();
await profileRepository.sendEmailCode();
@@ -19,7 +19,7 @@ class VerifyEmailCubit extends Cubit<VerifyEmailState> {
}
}
tryVerify(String code) async {
Future<void> tryVerify(String code) async {
try {
emit(const VerifyEmailState.verifyLoading());
final result = await profileRepository.tryVerifyEmailByCode(code);

View File

@@ -61,7 +61,7 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
);
}
onTapCalendar(BuildContext context) {
Null Function() onTapCalendar(BuildContext context) {
return () {
_showDialog(
CupertinoDatePicker(

View File

@@ -40,7 +40,7 @@ class SettingsCubit extends Cubit<SettingsState> {
}
}
updateProfileDetails({
Future<void> updateProfileDetails({
required ProfileDetailsModel profileDetailsModel,
}) async {
try {
@@ -54,7 +54,7 @@ class SettingsCubit extends Cubit<SettingsState> {
}
}
updateProfileSettings({
Future<void> updateProfileSettings({
required ProfileSettingsModel profileSettingsModel,
}) async {
try {

View File

@@ -151,7 +151,7 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
);
}
onTapCalendar(BuildContext context) {
Null Function() onTapCalendar(BuildContext context) {
return () {
_showDialog(
CupertinoDatePicker(
@@ -187,7 +187,7 @@ class _GeneralInfoScreenState extends State<GeneralInfoScreen> {
);
}
_saveButton() {
InkWell _saveButton() {
return InkWell(
onTap: () {
widget.settingsEntity.settingsCubit.updateProfileDetails(

Some files were not shown because too many files have changed in this diff Show More