forked from eatvio/eatvio_app
Merge pull request 'feat(deepLinks)' (#518) from misha/eatvio_app:deepLinks into main
Reviewed-on: eatvio/eatvio_app#518
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,4 +11,6 @@
|
||||
/android/app/upload-keystore.jks
|
||||
/android/app/.cxx
|
||||
*.g.dart
|
||||
*.freezed.dart
|
||||
*.freezed.dart
|
||||
android/build/
|
||||
ios/XCBuildData/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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!);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -6,7 +6,7 @@ class NewMessagesCubit extends Cubit<NewMessagesState> {
|
||||
|
||||
NewMessagesCubit() : super(const NewMessagesState.empty());
|
||||
|
||||
incrementCounter() {
|
||||
void incrementCounter() {
|
||||
_messagesCount++;
|
||||
emit(NewMessagesState.loaded(messagesCount: _messagesCount));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -167,7 +167,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
showImagePicker(String slug) {
|
||||
void showImagePicker(String slug) {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -35,7 +35,7 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_startRegistrationButton() {
|
||||
InkWell _startRegistrationButton() {
|
||||
return InkWell(
|
||||
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -148,7 +148,7 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_saveState() {
|
||||
void _saveState() {
|
||||
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
|
||||
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
|
||||
photos: imagesPaths,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -158,7 +158,7 @@ class _SocialNetworksScreenState extends State<SocialNetworksScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
setLinksState({
|
||||
void setLinksState({
|
||||
String? tgLink,
|
||||
String? vkLink,
|
||||
String? personalSiteLink,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class _ImageWidgetState extends State<ImageWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
_showImagePicker() {
|
||||
void _showImagePicker() {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
|
||||
@@ -112,7 +112,7 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
_modalBottomSheet({
|
||||
Future _modalBottomSheet({
|
||||
required CreateRecipeCubit createRecipeCubit,
|
||||
int? portionIndex,
|
||||
String? productNameControllerText,
|
||||
|
||||
@@ -82,7 +82,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
_showImagePicker() {
|
||||
void _showImagePicker() {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: (_) {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -86,7 +86,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
loadDiets({
|
||||
Future<void> loadDiets({
|
||||
String? query,
|
||||
}) async {
|
||||
try {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class PlansCubit extends Cubit<PlansState> {
|
||||
);
|
||||
}
|
||||
|
||||
loadPlans({
|
||||
Future<void> loadPlans({
|
||||
String? query,
|
||||
}) async {
|
||||
try {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -85,7 +85,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
|
||||
|
||||
|
||||
_onScrollConsultants() {
|
||||
void _onScrollConsultants() {
|
||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -42,7 +42,7 @@ class _TagsModalWidgetState extends State<TagsModalWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
_tagChip({
|
||||
ChoiceChip _tagChip({
|
||||
required RecipeTag tag,
|
||||
required bool isSelected,
|
||||
}) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -61,7 +61,7 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
onTapCalendar(BuildContext context) {
|
||||
Null Function() onTapCalendar(BuildContext context) {
|
||||
return () {
|
||||
_showDialog(
|
||||
CupertinoDatePicker(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user