Compare commits

...

20 Commits

Author SHA1 Message Date
misha
1d8f414672 Merge remote-tracking branch 'upstream/main' into deepLinks 2025-10-17 16:03:35 +03:00
49453c8b31 Merge pull request 'feat(pushNotifications)' (#708) from misha/eatvio_app:issue-707 into main
Reviewed-on: eatvio/eatvio_app#708
2025-10-17 12:25:36 +00:00
misha
138e2c1073 Merge remote-tracking branch 'upstream/main' into issue-707 2025-10-17 15:24:21 +03:00
eb39ae5e7c Merge pull request 'feat(diary)' (#705) from misha/eatvio_app:issue-701 into main
Reviewed-on: eatvio/eatvio_app#705
2025-10-17 11:52:09 +00:00
misha
250d9614e3 Merge remote-tracking branch 'upstream/main' into issue-701 2025-10-17 11:50:36 +03:00
b9b06f11a6 Merge pull request 'feat(glossary)' (#711) from misha/eatvio_app:issue-710 into main
Reviewed-on: eatvio/eatvio_app#711
2025-10-16 15:24:56 +00:00
misha
f35cdd26a3 feat(diary): fexed wrong day selecting logic 2025-10-06 13:33:12 +03:00
misha
5ee86ddc92 feat(diary): fixed wrong cpfc loading after changing a portion 2025-10-03 15:40:05 +03:00
misha
fceaf3d162 Merge remote-tracking branch 'upstream/main' into issue-701 2025-10-02 14:03:14 +03:00
misha
ca3bd7cc0d Merge remote-tracking branch 'upstream/main' into issue-707 2025-10-02 10:21:04 +03:00
misha
782f75d26b feat(diary): fixed cpfc for v2 diary 2025-09-29 09:49:59 +03:00
misha
79dc590beb Merge remote-tracking branch 'upstream/main' into issue-701 2025-09-25 15:14:25 +03:00
misha
517e493b5e feat(pushNotifications): added anonymous token for push notifications 2025-09-24 15:23:01 +03:00
misha
1bc0ab3237 fixed profile back button 2025-09-24 10:01:06 +03:00
misha
a8994eecf4 feat(diary): fixed bugs fod diary with v2 methods 2025-09-22 10:52:29 +03:00
misha
8d3ae15112 feat(diary): added v2 methods for diary 2025-09-18 18:47:07 +03:00
Username1548
6b7b1f2d18 feat(deepLinks): added diary deep link and improved profile and chat deep links 2025-09-10 15:19:49 +03:00
Username1548
82bc51e45c feat(deepLinks): added chat and profile deeplinks 2025-09-10 08:08:58 +03:00
Username1548
77666aca0e Merge remote-tracking branch 'upstream/main' into deepLinks 2025-09-09 21:24:36 +03:00
Username1548
16430af989 gitignore 2025-09-09 21:24:05 +03:00
39 changed files with 635 additions and 559 deletions

View File

@@ -63,6 +63,18 @@
<data android:pathPattern="/ru/plan/.*" /> <data android:pathPattern="/ru/plan/.*" />
<data android:pathPattern="/en/plan/.*" /> <data android:pathPattern="/en/plan/.*" />
<data android:pathPattern="/ru/chat/.*" />
<data android:pathPattern="/en/chat/.*" />
<data android:pathPattern="/ru/chat" />
<data android:pathPattern="/en/chat" />
<data android:pathPattern="/ru/profile/.*" />
<data android:pathPattern="/en/profile/.*" />
<data android:pathPattern="/ru/diary/.*" />
<data android:pathPattern="/en/diary/.*" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.

View File

@@ -29,23 +29,45 @@ class AppRouter {
/// Обработка редиректов для deep links /// Обработка редиректов для deep links
static String? _handleRedirect(BuildContext context, GoRouterState state) { static String? _handleRedirect(BuildContext context, GoRouterState state) {
final segments = state.uri.pathSegments; final segments = state.uri.pathSegments;
if (segments.length >= 3) { if (segments.length == 4) {
final type = segments[1];
switch (type) {
case 'diary':
FlutterNativeSplash.remove();
return '/diary-screen';
}
}
if (segments.length == 3) {
final type = segments[1]; final type = segments[1];
final contentIdentifier = segments[2]; final contentIdentifier = segments[2];
switch (type) { switch (type) {
case 'recipe': case 'recipe':
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
return '${RouteConstants.aboutRecipesScreen}?slug=$contentIdentifier'; return '/about-recipes-screen?slug=$contentIdentifier';
case 'blog_post': case 'blog_post':
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
return '${RouteConstants.blogPostScreen}?id=$contentIdentifier'; return '/blog-post-screen?id=$contentIdentifier';
case 'plan': case 'plan':
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
return '${RouteConstants.aboutPlanScreen}?slug=$contentIdentifier'; return '/about-plan-screen?slug=$contentIdentifier';
case 'diet': case 'diet':
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
return '${RouteConstants.aboutDietScreen}?slug=$contentIdentifier'; return '/about-diet-screen?slug=$contentIdentifier';
case 'chat':
FlutterNativeSplash.remove();
return '/chat-select-screen';
case 'profile':
FlutterNativeSplash.remove();
return '/profile-screen?slug=$contentIdentifier';
}
}
if (segments.length == 2) {
final type = segments[1];
switch (type) {
case 'chat':
FlutterNativeSplash.remove();
return '/chat-select-screen';
} }
} }

View File

@@ -2,7 +2,9 @@ import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart'; import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart'; import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Регистрация внешних зависимостей в системе внедрения зависимостей /// Регистрация внешних зависимостей в системе внедрения зависимостей
class ExternalRegistrations { class ExternalRegistrations {
@@ -11,23 +13,20 @@ class ExternalRegistrations {
/// Регистрация всех внешних зависимостей /// Регистрация всех внешних зависимостей
static void registerAll() { static void registerAll() {
_registerDio(); _registerDio();
_registerPrefs();
} }
/// Регистрация Dio HTTP клиента /// Регистрация Dio HTTP клиента
static void _registerDio() { static void _registerDio() {
sl.registerLazySingleton( sl.registerLazySingleton(
() => Dio( () =>
BaseOptions( Dio(BaseOptions(baseUrl: sl<AppConfig>().baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
baseUrl: sl<AppConfig>().baseUrl, ..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
headers: {
'accept': 'application/json',
'content-type': 'application/json',
},
),
)..interceptors.addAll([
TokenInterceptor(),
PhoneVerifyErrorInterceptor(),
]),
); );
} }
/// Регистрация Prefs
static void _registerPrefs() {
sl.registerSingletonAsync<Prefs>(()async => Prefs(await SharedPreferences.getInstance()));
}
} }

View File

@@ -29,6 +29,22 @@ class FirebaseApi {
return token; return token;
} }
Future<void> registerAnonymousPushToken() async {
try {
final deviceInfo = DeviceInfoPlugin();
await sl<Dio>().post(
'/api/anonymous_token',
data: {
'platform': Platform.isIOS ? 'ios' : 'android',
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
'token': await getToken(),
},
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
}
Future<void> registerPushToken() async { Future<void> registerPushToken() async {
if (await TokenStorage.haveTokenInStorage()) { if (await TokenStorage.haveTokenInStorage()) {
try { try {

43
lib/core/prefs.dart Normal file
View File

@@ -0,0 +1,43 @@
import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Prefs {
final SharedPreferences _prefs;
Prefs(this._prefs);
String _key(PrefsKeys key) => key.name;
Future<void> setString(PrefsKeys key, String value) async {
await _prefs.setString(_key(key), value);
}
String? getString(PrefsKeys key) {
return _prefs.getString(_key(key));
}
Future<void> setInt(PrefsKeys key, int value) async {
await _prefs.setInt(_key(key), value);
}
int? getInt(PrefsKeys key) {
return _prefs.getInt(_key(key));
}
Future<void> setBool(PrefsKeys key, bool value) async {
await _prefs.setBool(_key(key), value);
}
bool? getBool(PrefsKeys key) {
return _prefs.getBool(_key(key));
}
Future<void> remove(PrefsKeys key) async {
await _prefs.remove(_key(key));
}
Future<void> clear() async {
await _prefs.clear();
}
}

1
lib/core/prefs_keys.dart Normal file
View File

@@ -0,0 +1 @@
enum PrefsKeys { hasAnonymousTokenBeenSent, welcomeScreenWasShowed }

View File

@@ -1,3 +1,5 @@
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/routing/route_constants.dart'; import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart'; import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart'; import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
@@ -18,6 +20,10 @@ class AuthRoutes {
path: RouteConstants.authScreen, path: RouteConstants.authScreen,
name: 'authScreen', name: 'authScreen',
builder: (context, state) => const AuthScreen(), builder: (context, state) => const AuthScreen(),
redirect: (context, state) {
sl<FirebaseApi>().registerAnonymousPushToken();
return null;
},
routes: [ routes: [
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()), GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
GoRoute( GoRoute(

View File

@@ -2,6 +2,8 @@ import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:eatvio_app/core/routing/route_constants.dart'; import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart'; import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
@@ -27,6 +29,7 @@ import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart'; 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/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart'; import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart'; import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
@@ -40,7 +43,6 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/presentat
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:version/version.dart'; import 'package:version/version.dart';
/// Остальные маршруты приложения /// Остальные маршруты приложения
@@ -55,18 +57,24 @@ class OtherRoutes {
builder: (context, state) => const WelcomeScreen(), builder: (context, state) => const WelcomeScreen(),
redirect: (context, state) async { redirect: (context, state) async {
try { try {
final sharedPreferences = await SharedPreferences.getInstance(); final prefs = sl<Prefs>();
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false; final firebaseApi = sl<FirebaseApi>();
await sl<FirebaseApi>().registerPushToken(); final welcomeScreenWasShowed = prefs.getBool(PrefsKeys.welcomeScreenWasShowed) ?? false;
if (welcomeScreenWasShowed == false) { await firebaseApi.registerPushToken();
if (!welcomeScreenWasShowed) {
return null; return null;
} else if (await TokenStorage.haveTokenInStorage()) { } else if (await TokenStorage.haveTokenInStorage()) {
return RouteConstants.feedScreen; return '/feed-screen';
} else { } else {
return RouteConstants.authScreen; final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
if (!hasAnonymousTokenBeenSent) {
await firebaseApi.registerAnonymousPushToken();
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
}
return '/auth-screen';
} }
} catch (e) { } catch (e) {
return RouteConstants.feedScreen; return '/feed-screen';
} }
}, },
), ),
@@ -139,7 +147,13 @@ class OtherRoutes {
GoRoute( GoRoute(
path: RouteConstants.profileScreen, path: RouteConstants.profileScreen,
name: 'profileScreen', name: 'profileScreen',
builder: (context, state) => ProfileScreen(author: state.extra as Author), builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return ProfileScreen(slug: slug);
}
return ProfileScreen(slug: state.extra as String);
},
), ),
GoRoute( GoRoute(
path: RouteConstants.subsScreen, path: RouteConstants.subsScreen,
@@ -161,7 +175,7 @@ class OtherRoutes {
GoRoute( GoRoute(
path: RouteConstants.emptyChatScreen, path: RouteConstants.emptyChatScreen,
name: 'emptyChatScreen', name: 'emptyChatScreen',
builder: (context, state) => EmptyChatScreen(author: state.extra as Author), builder: (context, state) => EmptyChatScreen(user: state.extra as ProfileModel),
), ),
// Consultant routes // Consultant routes

View File

@@ -46,6 +46,17 @@ extension DateTimeExtension on DateTime {
final now = DateTime.now(); final now = DateTime.now();
return now.year == localDate.year && now.month == localDate.month && now.day == localDate.day; return now.year == localDate.year && now.month == localDate.month && now.day == localDate.day;
} }
String toISO8601WithTimeZoneOffset() {
final formatter = DateFormat("yyyy-MM-ddTHH:mm:ss");
final formatted = formatter.format(this);
final offset = timeZoneOffset;
final hours = offset.inHours.abs().toString().padLeft(2, '0');
final minutes = (offset.inMinutes.abs() % 60).toString().padLeft(2, '0');
final sign = offset.isNegative ? '-' : '+';
return "$formatted$sign$hours:$minutes";
}
} }
String calculateDifference(DateTime dateTime, [String? format]) { String calculateDifference(DateTime dateTime, [String? format]) {

View File

@@ -198,7 +198,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
child: Column( child: Column(
children: [ children: [
GestureDetector( GestureDetector(
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: blogPostState.blogPostModel?.author), onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: blogPostState.blogPostModel?.author.authorSlug),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(

View File

@@ -18,7 +18,6 @@ import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
@@ -152,12 +151,7 @@ class _ChatScreenState extends State<ChatScreen> {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: state.chatRoom.users.receiver.slug,
authorSlug: state.chatRoom.users.receiver.slug,
authorName: state.chatRoom.users.receiver.name,
authorProfileIcon: state.chatRoom.users.receiver.profilePhotoUrl ?? '',
authorIsMe: false,
),
); );
}, },
child: Row( child: Row(

View File

@@ -6,7 +6,7 @@ import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -16,12 +16,9 @@ import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
class EmptyChatScreen extends StatefulWidget { class EmptyChatScreen extends StatefulWidget {
final Author author; final ProfileModel user;
const EmptyChatScreen({ const EmptyChatScreen({super.key, required this.user});
super.key,
required this.author,
});
@override @override
State<EmptyChatScreen> createState() => _EmptyChatScreenState(); State<EmptyChatScreen> createState() => _EmptyChatScreenState();
@@ -40,34 +37,19 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
centerTitle: false, centerTitle: false,
title: GestureDetector( title: GestureDetector(
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed('profileScreen', extra: widget.user.slug);
'profileScreen',
extra: Author(
authorSlug: widget.author.authorSlug,
authorName: widget.author.authorName,
authorProfileIcon: widget.author.authorProfileIcon,
authorIsMe: false,
),
);
}, },
child: Row( child: Row(
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 20, radius: 20,
foregroundImage: NetworkImage(widget.author.authorProfileIcon), foregroundImage: NetworkImage(widget.user.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'), backgroundImage: const AssetImage('assets/images/no_avatar.png'),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [Text(widget.user.name, style: CustomTextStyles.titleSmallBold.copyWith(color: PrimaryColors().black))],
Text(
widget.author.authorName,
style: CustomTextStyles.titleSmallBold.copyWith(
color: PrimaryColors().black,
),
),
],
), ),
], ],
), ),
@@ -84,18 +66,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
children: [ children: [
const SizedBox(width: 8), const SizedBox(width: 8),
InkWell( InkWell(
onTap: () => showImagePicker(widget.author.authorSlug), onTap: () => showImagePicker(widget.user.slug),
customBorder: const CircleBorder(), customBorder: const CircleBorder(),
child: Ink( child: Ink(
height: 40, height: 40,
width: 40, width: 40,
child: Transform.rotate( child: Transform.rotate(
angle: pi / 4, angle: pi / 4,
child: const Icon( child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99)),
Icons.attach_file_rounded,
size: 30,
color: Color(0xFF858E99),
),
), ),
), ),
), ),
@@ -117,8 +95,10 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
hintText: 'Сообщение', hintText: 'Сообщение',
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
filled: true, filled: true,
border: border: OutlineInputBorder(
OutlineInputBorder(borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)), borderRadius: BorderRadius.circular(100),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(100), borderRadius: BorderRadius.circular(100),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
@@ -141,14 +121,11 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
final blockedState = blockedUsersCubit.state; final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) { if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug); final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.user.slug);
if (isBlocked) { if (isBlocked) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Вы не можете отправить сообщение заблокированному пользователю'), backgroundColor: Colors.red),
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
backgroundColor: Colors.red,
),
); );
} }
return; return;
@@ -156,23 +133,15 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
} }
if (textEditingController.text.isNotEmpty) { if (textEditingController.text.isNotEmpty) {
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(data: textEditingController.text.trim(), slug: widget.user.slug);
data: textEditingController.text.trim(),
slug: widget.author.authorSlug,
);
if (mounted) { if (mounted) {
setState(() { setState(() {
textEditingController.clear(); textEditingController.clear();
}); });
} }
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug); final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.user.slug);
if (context.mounted) { if (context.mounted) {
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( AppRouter.navigatorKey.currentContext?.pushReplacementNamed('chatScreen', extra: {'uuid': chatIdOrError});
'chatScreen',
extra: {
'uuid': chatIdOrError,
},
);
} }
} }
}, },
@@ -208,14 +177,11 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
final blockedState = blockedUsersCubit.state; final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) { if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug); final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.user.slug);
if (isBlocked) { if (isBlocked) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Вы не можете отправить сообщение заблокированному пользователю'), backgroundColor: Colors.red),
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
backgroundColor: Colors.red,
),
); );
} }
return; return;
@@ -226,28 +192,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
List<XFile>? images = await picker.pickMultiImage(); List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty) { if (images.isNotEmpty) {
List<File> files = images.map((image) => File(image.path)).toList(); List<File> files = images.map((image) => File(image.path)).toList();
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(files: files, slug: widget.user.slug);
files: files, final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.user.slug);
slug: widget.author.authorSlug,
);
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
if (mounted) { if (mounted) {
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( AppRouter.navigatorKey.currentContext?.pushReplacementNamed('chatScreen', extra: {'uuid': chatIdOrError});
'chatScreen',
extra: {
'uuid': chatIdOrError,
},
);
} }
} }
}, },
child: const Row( child: const Row(children: [Icon(Icons.photo_library_rounded), SizedBox(width: 8), Text('Галерея')]),
children: [
Icon(Icons.photo_library_rounded),
SizedBox(width: 8),
Text('Галерея'),
],
),
), ),
CupertinoActionSheetAction( CupertinoActionSheetAction(
onPressed: () async { onPressed: () async {
@@ -256,28 +208,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
List<XFile>? images = await picker.pickMultiImage(); List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty) { if (images.isNotEmpty) {
List<File> files = images.map((image) => File(image.path)).toList(); List<File> files = images.map((image) => File(image.path)).toList();
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(files: files, slug: widget.user.slug);
files: files, final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.user.slug);
slug: widget.author.authorSlug,
);
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
if (mounted) { if (mounted) {
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( AppRouter.navigatorKey.currentContext?.pushReplacementNamed('chatScreen', extra: {'uuid': chatIdOrError});
'chatScreen',
extra: {
'uuid': chatIdOrError,
},
);
} }
} }
}, },
child: const Row( child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
), ),
CupertinoActionSheetAction( CupertinoActionSheetAction(
onPressed: () async { onPressed: () async {
@@ -308,35 +246,17 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
); );
if (result != null) { if (result != null) {
List<File> files = result.paths.map((path) => File(path!)).toList(); List<File> files = result.paths.map((path) => File(path!)).toList();
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(files: files, slug: widget.user.slug);
files: files, final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.user.slug);
slug: widget.author.authorSlug,
);
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
if (mounted) { if (mounted) {
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( AppRouter.navigatorKey.currentContext?.pushReplacementNamed('chatScreen', extra: {'uuid': chatIdOrError});
'chatScreen',
extra: {
'uuid': chatIdOrError,
},
);
} }
} }
}, },
child: const Row( child: const Row(children: [Icon(Icons.file_present_rounded), SizedBox(width: 8), Text('Файлы')]),
children: [
Icon(Icons.file_present_rounded),
SizedBox(width: 8),
Text('Файлы'),
],
),
), ),
], ],
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(onPressed: () => Navigator.of(context).pop(), isDestructiveAction: true, child: const Text('Отмена')),
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
), ),
); );
} }

View File

@@ -0,0 +1,22 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'diary_day_entity.freezed.dart';
@freezed
abstract class DiaryDayEntity with _$DiaryDayEntity {
factory DiaryDayEntity({
@JsonKey(name: "id") required int id,
@JsonKey(name: "date") required String date,
@JsonKey(name: "wakeup") String? wakeup,
@JsonKey(name: "retire") String? retire,
@JsonKey(name: "closed") required int closed,
@JsonKey(name: "comment") required String comment,
@JsonKey(name: "state") required TypeOfDiaryModel typeOfDiary,
@JsonKey(name: "meals") required List<MealModel> meals,
@JsonKey(name: "chosen_plan_id") required int? chosenPlanId,
@JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet,
@JsonKey(name: "chosen_diet", fromJson: parseDiet) required DiaryDietModel? diet,
}) = _DiaryDayEntity;
}

View File

@@ -0,0 +1,18 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'diary_future_diet_day_entity.freezed.dart';
@freezed
abstract class DiaryFutureDietDayEntity with _$DiaryFutureDietDayEntity {
const factory DiaryFutureDietDayEntity({
required String date,
required TypeOfDiaryModel typeOfDiary,
required List<MealModel> meals,
required DiaryDietModel? diet,
required int? dayOfChosenDiet,
}) = _DiaryFutureDietDayEntity;
}

View File

@@ -0,0 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'create_day_model.freezed.dart';
part 'create_day_model.g.dart';
@freezed
abstract class CreateDayModel with _$CreateDayModel {
factory CreateDayModel({required String day}) = _CreateDayModel;
factory CreateDayModel.fromJson(Map<String, dynamic> json) => _$CreateDayModelFromJson(json);
}

View File

@@ -1,4 +1,6 @@
import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'diary_day_model.freezed.dart'; part 'diary_day_model.freezed.dart';
@@ -30,7 +32,7 @@ abstract class DiaryDayWrapperModel with _$DiaryDayWrapperModel {
@freezed @freezed
abstract class DiaryDayModel with _$DiaryDayModel { abstract class DiaryDayModel with _$DiaryDayModel {
const factory DiaryDayModel({ const factory DiaryDayModel({
@JsonKey(name: "id") required int id, @JsonKey(name: "id") required int? id,
@JsonKey(name: "date") required String date, @JsonKey(name: "date") required String date,
@JsonKey(name: "wakeup") String? wakeup, @JsonKey(name: "wakeup") String? wakeup,
@JsonKey(name: "retire") String? retire, @JsonKey(name: "retire") String? retire,
@@ -40,26 +42,36 @@ abstract class DiaryDayModel with _$DiaryDayModel {
@JsonKey(name: "meals") required List<MealModel> meals, @JsonKey(name: "meals") required List<MealModel> meals,
@JsonKey(name: "chosen_plan_id") required int? chosenPlanId, @JsonKey(name: "chosen_plan_id") required int? chosenPlanId,
@JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet, @JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet,
@JsonKey(name: "chosen_diet", fromJson: _parseDiet) required DiaryDietModel? diet, @JsonKey(name: "chosen_diet", fromJson: parseDiet) required DiaryDietModel? diet,
}) = _DiaryDayModel; }) = _DiaryDayModel;
const DiaryDayModel._();
factory DiaryDayModel.fromJson(Map<String, dynamic> json) => _$DiaryDayModelFromJson(json); factory DiaryDayModel.fromJson(Map<String, dynamic> json) => _$DiaryDayModelFromJson(json);
DiaryDayEntity? toEntity() {
if (id == null) {
return null;
}
return DiaryDayEntity(
id: id!,
date: date,
closed: closed,
comment: comment,
typeOfDiary: typeOfDiary,
meals: meals,
chosenPlanId: chosenPlanId,
dayOfChosenDiet: dayOfChosenDiet,
wakeup: wakeup,
retire: retire,
diet: diet,
);
}
DiaryFutureDietDayEntity toDiaryFutureDayEntity() {
return DiaryFutureDietDayEntity(date: date, typeOfDiary: typeOfDiary, meals: meals, dayOfChosenDiet: dayOfChosenDiet, diet: diet);
}
} }
@freezed DiaryDietModel? parseDiet(dynamic json) {
abstract class DiaryFutureDietDayModel with _$DiaryFutureDietDayModel {
const factory DiaryFutureDietDayModel({
@JsonKey(name: "date") required String date,
@JsonKey(name: "state", fromJson: _parseDayTypeForFutureDay) required TypeOfDiaryModel typeOfDiary,
@JsonKey(name: "meals") required List<MealModel> meals,
@JsonKey(name: "chosen_diet", fromJson: _parseDiet) required DiaryDietModel? diet,
@JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet,
}) = _DiaryFutureDietDayModel;
factory DiaryFutureDietDayModel.fromJson(Map<String, dynamic> json) => _$DiaryFutureDietDayModelFromJson(json);
}
DiaryDietModel? _parseDiet(dynamic json) {
if (json == null) return null; if (json == null) return null;
return DiaryDietModel.fromJson(json['diet']); return DiaryDietModel.fromJson(json['diet']);
} }
@@ -71,10 +83,6 @@ abstract class DiaryDietModel with _$DiaryDietModel {
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json); factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
} }
TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) {
return TypeOfDiaryModel(hasChosenDiet: json['has_chosen_diet'] as bool, hasChosenPlan: json['has_chosen_plan'] as bool, diaryState: DiaryStateEnum.init);
}
@freezed @freezed
abstract class MealModel with _$MealModel { abstract class MealModel with _$MealModel {
const factory MealModel({ const factory MealModel({
@@ -141,7 +149,7 @@ abstract class TypeOfDiaryModel with _$TypeOfDiaryModel {
const factory TypeOfDiaryModel({ const factory TypeOfDiaryModel({
@JsonKey(name: "has_chosen_diet") required bool hasChosenDiet, @JsonKey(name: "has_chosen_diet") required bool hasChosenDiet,
@JsonKey(name: "has_chosen_plan") required bool hasChosenPlan, @JsonKey(name: "has_chosen_plan") required bool hasChosenPlan,
@JsonKey(name: "last_state") required DiaryStateEnum diaryState, @JsonKey(name: "last_state") required DiaryStateEnum? diaryState,
}) = _TypeOfDiaryModel; }) = _TypeOfDiaryModel;
factory TypeOfDiaryModel.fromJson(Map<String, dynamic> json) => _$TypeOfDiaryModelFromJson(json); factory TypeOfDiaryModel.fromJson(Map<String, dynamic> json) => _$TypeOfDiaryModelFromJson(json);

View File

@@ -0,0 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'get_day_model.freezed.dart';
part 'get_day_model.g.dart';
@freezed
abstract class GetDayModel with _$GetDayModel {
factory GetDayModel({required String day}) = _GetDayModel;
factory GetDayModel.fromJson(Map<String, dynamic> json) => _$GetDayModelFromJson(json);
}

View File

@@ -1,10 +1,13 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
@@ -20,7 +23,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryCubit extends Cubit<DiaryState> { class DiaryCubit extends Cubit<DiaryState> {
late final DateTime today; late final DateTime today;
late DateTime selectedDay; late DateTime selectedDay;
late int selectedMeal; int selectedMeal = 1;
final DiaryRepository diaryRepository; final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository; final GlossaryRepository glossaryRepository;
final ProfileRepository profileRepository; final ProfileRepository profileRepository;
@@ -39,19 +42,79 @@ class DiaryCubit extends Cubit<DiaryState> {
today = DateTime.now(); today = DateTime.now();
selectedDay = today; selectedDay = today;
diaryRepository.forceFetchStream.listen((_) { diaryRepository.forceFetchStream.listen((_) {
fetchDiary(true, diaryId); fetchDiaryV2(needLoading: true, diaryId: diaryId);
}); });
} }
String comment = ''; String comment = '';
Future<bool?> setSelectedDay(DateTime day, {bool? forDietOnly}) async { Future<bool> setSelectedDay(DateTime day) async {
final prevDay = selectedDay;
selectedDay = day; selectedDay = day;
if (forDietOnly ?? false) { final canSelect = await fetchDiaryV2(needLoading: true);
final result = await fetchDiaryForDietOnly();
return result; if (!canSelect) {
selectedDay = prevDay;
}
return canSelect;
}
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
final currentState = state;
if (needLoading) {
emit(const DiaryState.loading());
}
if (diaryId != null) {
final diaryDayById = await diaryRepository.getDiaryDay(diaryId);
final diaryEntity = diaryDayById.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
return true;
}
}
final diaryDay = await getDiary();
if (diaryDay == null) {
emit(const DiaryState.error(errorMessage: 'Diary not found'));
return true;
}
final diaryEntity = diaryDay.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
return true;
}
if (diaryDay.typeOfDiary.hasChosenDiet == true && diaryDay.typeOfDiary.diaryState == null) {
setDiaryForDietOnly(diaryDay.toDiaryFutureDayEntity());
return true;
}
emit(currentState);
return false;
}
Future<void> updateCurrentDiary() async {
try {
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
final diaryEntity = diaryDay.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
}
} catch (e) {
log(e.toString());
}
}
Future<DiaryDayModel?> getDiary() async {
try {
await diaryRepository.createDiaryDay(day: selectedDay.toISO8601WithTimeZoneOffset());
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
return diaryDay;
} on DioException catch (e) {
if (e.response?.statusCode == 422) {
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
return diaryDay;
}
} }
await fetchDiary(true);
return null; return null;
} }
@@ -65,9 +128,7 @@ class DiaryCubit extends Cubit<DiaryState> {
} }
} }
Future<bool> fetchDiaryForDietOnly() async { Future<bool> setDiaryForDietOnly(DiaryFutureDietDayEntity diaryDay) async {
final stringDate = selectedDay.format('yyyy-MM-dd');
final diaryDay = await diaryRepository.getDiaryByDateForDietOnly(stringDate);
if (!diaryDay.typeOfDiary.hasChosenDiet) { if (!diaryDay.typeOfDiary.hasChosenDiet) {
return false; return false;
} }
@@ -146,22 +207,14 @@ class DiaryCubit extends Cubit<DiaryState> {
return false; return false;
} }
Future<void> fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async { Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async {
this.diaryId ??= diaryId; this.diaryId ??= diaryId;
final isConsultantView = this.diaryId != null; final isConsultantView = this.diaryId != null;
if (needLoading) {
emit(const DiaryState.loading());
selectedMeal = 1;
}
try { try {
final DiaryDayModel diaryDay;
bool isMoodEnabled = false; bool isMoodEnabled = false;
bool isWakeUpAndRetireTimeEnabled = false; bool isWakeUpAndRetireTimeEnabled = false;
if (diaryId != null) { if (diaryId != null) {
diaryDay = await diaryRepository.getDiaryDay(diaryId);
} else { } else {
final stringDate = selectedDay.format('yyyy-MM-dd');
diaryDay = await diaryRepository.getDiaryByDate(stringDate);
final profileSettings = await settingsRepository.getProfileSettings(); final profileSettings = await settingsRepository.getProfileSettings();
isMoodEnabled = profileSettings.isMoodFillable; isMoodEnabled = profileSettings.isMoodFillable;
isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable; isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable;
@@ -214,7 +267,7 @@ class DiaryCubit extends Cubit<DiaryState> {
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async { Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment); await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
await fetchDiary(); await updateCurrentDiary();
} }
Future<NutritionDataModel> getNutrition(int index) async { Future<NutritionDataModel> getNutrition(int index) async {
@@ -238,7 +291,7 @@ class DiaryCubit extends Cubit<DiaryState> {
quantity: quantity, quantity: quantity,
productNutritionalValueId: productNutritionalValueId, productNutritionalValueId: productNutritionalValueId,
); );
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> updateProductInMeal({ Future<void> updateProductInMeal({
@@ -272,27 +325,27 @@ class DiaryCubit extends Cubit<DiaryState> {
portionState: portionState, portionState: portionState,
); );
} }
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary(); await updateCurrentDiary();
} }
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
@@ -301,12 +354,12 @@ class DiaryCubit extends Cubit<DiaryState> {
Future<void> 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 diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
await fetchDiary(); await updateCurrentDiary();
} }
Future<GlossaryModerationModel?> sendProductToModeration({ Future<GlossaryModerationModel?> sendProductToModeration({
@@ -326,30 +379,32 @@ class DiaryCubit extends Cubit<DiaryState> {
Future<void> 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 diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.fillWakeup(diaryId: diaryId, time: time);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.fillRetire(diaryId: diaryId, time: time);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
await fetchDiary(); await updateCurrentDiary();
} }
Future<void> 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 diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
await fetchDiary(); await updateCurrentDiary();
} }
void fillComment(String comment) { void fillComment(String comment) {
this.comment = comment; this.comment = comment;
} }
} }
enum DiaryFetchingErrors { canNotCreateFutureDays, dayAlreadyExists }

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,12 +12,10 @@ abstract class DiaryState with _$DiaryState {
const factory DiaryState.loading() = DiaryStateLoading; const factory DiaryState.loading() = DiaryStateLoading;
const factory DiaryState.error({ const factory DiaryState.error({required String errorMessage}) = DiaryStateError;
required String errorMessage,
}) = DiaryStateError;
const factory DiaryState.loaded({ const factory DiaryState.loaded({
required DiaryDayModel diary, required DiaryDayEntity diary,
required NutritionDataModel nutrition, required NutritionDataModel nutrition,
required DateTime selectedDay, required DateTime selectedDay,
required int selectedMeal, required int selectedMeal,
@@ -32,9 +31,6 @@ abstract class DiaryState with _$DiaryState {
@Default(false) bool isLoadingAddingProductInMeal, @Default(false) bool isLoadingAddingProductInMeal,
}) = DiaryStateLoaded; }) = DiaryStateLoaded;
const factory DiaryState.loadedTomorrowWithDiet({ const factory DiaryState.loadedTomorrowWithDiet({required DiaryFutureDietDayEntity diary, required DateTime selectedDay, required int selectedMeal}) =
required DiaryFutureDietDayModel diary, DiaryStateLoadedTomorrowWithDiet;
required DateTime selectedDay,
required int selectedMeal,
}) = DiaryStateLoadedTomorrowWithDiet;
} }

View File

@@ -35,19 +35,12 @@ class DiaryScreen extends StatelessWidget {
class UserDiaryScreen extends StatelessWidget { class UserDiaryScreen extends StatelessWidget {
static const path = '/user-diary-screen'; static const path = '/user-diary-screen';
static const name = 'userDiaryScreen'; static const name = 'userDiaryScreen';
const UserDiaryScreen({ const UserDiaryScreen({super.key, required this.diaryId, required this.currentPlanDay});
super.key,
required this.diaryId,
required this.currentPlanDay,
});
final int diaryId; final int diaryId;
final int currentPlanDay; final int currentPlanDay;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DiaryScreenContent( return DiaryScreenContent(diaryId: diaryId, currentPlanDay: currentPlanDay);
diaryId: diaryId,
currentPlanDay: currentPlanDay,
);
} }
} }
@@ -60,12 +53,8 @@ class DiaryScreenContent extends StatelessWidget {
final s = S.of(context); final s = S.of(context);
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<DiaryCubit>( BlocProvider<DiaryCubit>(create: (BuildContext context) => sl<DiaryCubit>()..fetchDiaryV2(needLoading: true, diaryId: diaryId)),
create: (BuildContext context) => sl<DiaryCubit>()..fetchDiary(true, diaryId, currentPlanDay), BlocProvider(create: (context) => sl<ConsultantCommentsCubit>()),
),
BlocProvider(
create: (context) => sl<ConsultantCommentsCubit>(),
),
], ],
child: BlocBuilder<DiaryCubit, DiaryState>( child: BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) { builder: (BuildContext context, DiaryState state) {
@@ -74,16 +63,14 @@ class DiaryScreenContent extends StatelessWidget {
} }
if (state is DiaryStateError) { if (state is DiaryStateError) {
return Scaffold( return Scaffold(
body: DataErrorWidget(onReload: () => context.read<DiaryCubit>().fetchDiary(false, diaryId)), body: DataErrorWidget(onReload: () => context.read<DiaryCubit>().fetchDiaryV2(needLoading: false, diaryId: diaryId)),
); );
} }
return Scaffold( return Scaffold(
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.grey.shade200,
appBar: diaryId == null appBar: diaryId == null
? AppBar( ? AppBar(toolbarHeight: 0)
toolbarHeight: 0,
)
: AppBar( : AppBar(
title: Text( title: Text(
state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '', state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '',
@@ -97,12 +84,7 @@ class DiaryScreenContent extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
if (diaryId == null) const CalendarWidget(), if (diaryId == null) const CalendarWidget(),
if (state is DiaryStateLoading) ...[ if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()],
const SizedBox(
height: 60,
),
const Loader()
],
if (state is DiaryStateLoaded) if (state is DiaryStateLoaded)
WakeupAndRetireWidget( WakeupAndRetireWidget(
isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled, isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled,
@@ -114,40 +96,31 @@ class DiaryScreenContent extends StatelessWidget {
showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled, showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled,
showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented, showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented,
), ),
if (state is DiaryStateLoadedTomorrowWithDiet) if (state is DiaryStateLoadedTomorrowWithDiet) const MealWidget(showMood: false, showAddButton: false),
const MealWidget(
showMood: false,
showAddButton: false,
),
if (state is DiaryStateLoaded) const CaloriesChartWidget(), if (state is DiaryStateLoaded) const CaloriesChartWidget(),
const SizedBox(height: 12), const SizedBox(height: 12),
Builder(builder: (context) { Builder(
if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) { builder: (context) {
if (state.diary.comment.isNotEmpty) { if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) {
return CommentForConsultantWidget( if (state.diary.comment.isNotEmpty) {
comment: state.diary.comment, return CommentForConsultantWidget(comment: state.diary.comment);
); }
if (state.isDiaryCommented || state.isDiaryFinished) {
return const SizedBox.shrink();
}
return const CommentForConsultantWidget();
} }
if (state.isDiaryCommented || state.isDiaryFinished) { return const SizedBox.shrink();
return const SizedBox.shrink(); },
} ),
return const CommentForConsultantWidget();
}
return const SizedBox.shrink();
}),
if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented)
Padding( Padding(
padding: const EdgeInsets.only(top: 12), padding: const EdgeInsets.only(top: 12),
child: FinishDayButtonWidget( child: FinishDayButtonWidget(diaryDay: state.diary),
diaryDay: state.diary,
),
), ),
if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[ if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
const MainBackgroundBouble( const MainBackgroundBouble(padding: EdgeInsets.all(16), child: DayCommentWidget()),
padding: EdgeInsets.all(16),
child: DayCommentWidget(),
)
], ],
if (state is DiaryStateLoaded && state.consultantReview?.consultantComments.dayComment != null) ...[ if (state is DiaryStateLoaded && state.consultantReview?.consultantComments.dayComment != null) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -155,10 +128,7 @@ class DiaryScreenContent extends StatelessWidget {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
Text( Text(s.dayComment, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
s.dayComment,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 12), const SizedBox(height: 12),
CommentContainer(comment: state.consultantReview?.consultantComments.dayComment ?? ''), CommentContainer(comment: state.consultantReview?.consultantComments.dayComment ?? ''),
], ],

View File

@@ -25,26 +25,30 @@ class _AddProductModalState extends State<AddProductModal> {
final quantityController = TextEditingController(); final quantityController = TextEditingController();
GlossaryModel? selectedGlossary; GlossaryModel? selectedGlossary;
List<GlossaryModel> glossariesList = []; List<GlossaryModel> glossariesList = [];
late final String? _previousProductName;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.portion != null) { if (widget.portion != null) {
_previousProductName = widget.portion!.productName;
productNameController.text = widget.portion!.productName; productNameController.text = widget.portion!.productName;
quantityController.text = '${widget.portion!.quantity}'; quantityController.text = '${widget.portion!.quantity}';
selectedGlossary = GlossaryModel( final productNutritionalValueId = widget.portion?.productNutritionalValueId;
id: widget.portion!.id, if (productNutritionalValueId != null) {
name: widget.portion!.productName, selectedGlossary = GlossaryModel(
grInPortion: widget.portion!.portion == 'gr' ? 0 : 1, id: productNutritionalValueId,
energyOn100Gr: 0, name: widget.portion!.productName,
proteinsOn100Gr: 0, grInPortion: widget.portion!.portion == 'gr' ? 0 : 1,
fatsOn100Gr: 0, energyOn100Gr: 0,
carbohydratesOn100Gr: 0, proteinsOn100Gr: 0,
energyOnPortion: 0, fatsOn100Gr: 0,
proteinsOnPortion: 0, carbohydratesOn100Gr: 0,
fatsOnPortion: 0, energyOnPortion: 0,
carbohydratesOnPortion: 0, proteinsOnPortion: 0,
); fatsOnPortion: 0,
carbohydratesOnPortion: 0,
);
}
} }
} }
@@ -191,7 +195,7 @@ class _AddProductModalState extends State<AddProductModal> {
); );
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
if (selectedGlossary == null) { if (selectedGlossary == null && _previousProductName != productNameController.text) {
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
useRootNavigator: true, useRootNavigator: true,

View File

@@ -43,46 +43,24 @@ class _CalendarWidgetState extends State<CalendarWidget> {
locale: 'ru_RU', locale: 'ru_RU',
startingDayOfWeek: StartingDayOfWeek.monday, startingDayOfWeek: StartingDayOfWeek.monday,
calendarFormat: _calendarFormat, calendarFormat: _calendarFormat,
headerStyle: const HeaderStyle( headerStyle: const HeaderStyle(titleCentered: true, formatButtonVisible: false, leftChevronVisible: false, rightChevronVisible: false),
titleCentered: true,
formatButtonVisible: false,
leftChevronVisible: false,
rightChevronVisible: false,
),
onDaySelected: (selectedDay, focusedDay) async { onDaySelected: (selectedDay, focusedDay) async {
if (!isSameDay(_selectedDay, selectedDay)) { if (!isSameDay(_selectedDay, selectedDay)) {
final today = DateTime.now(); final previousSelectedDay = _selectedDay;
if (selectedDay.isAfter(today)) { final previousFocusedDay = _selectedDay;
if ((state is DiaryStateLoaded)) {
final result = await context.read<DiaryCubit>().setSelectedDay(_selectedDay, forDietOnly: true);
if (result == false && context.mounted) {
showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day);
return;
}
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
return;
}
if (state is DiaryStateLoadedTomorrowWithDiet) {
final result = await context.read<DiaryCubit>().setSelectedDay(_selectedDay, forDietOnly: true);
if (result == false && context.mounted) {
showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day);
return;
}
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
return;
}
}
setState(() { setState(() {
_selectedDay = selectedDay; _selectedDay = selectedDay;
_focusedDay = focusedDay; _focusedDay = focusedDay;
}); });
context.read<DiaryCubit>().setSelectedDay(_selectedDay); final canOpenNextDay = await context.read<DiaryCubit>().setSelectedDay(selectedDay);
if (!canOpenNextDay && context.mounted) {
showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day);
setState(() {
_selectedDay = previousSelectedDay;
_focusedDay = previousFocusedDay;
});
return;
}
} }
}, },
selectedDayPredicate: (day) { selectedDayPredicate: (day) {
@@ -101,26 +79,12 @@ class _CalendarWidgetState extends State<CalendarWidget> {
} }
}, },
calendarStyle: CalendarStyle( calendarStyle: CalendarStyle(
weekendDecoration: const BoxDecoration( weekendDecoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
color: Colors.white, defaultDecoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
shape: BoxShape.circle, selectedDecoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
), todayTextStyle: const TextStyle(color: Colors.black),
defaultDecoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
selectedDecoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
todayTextStyle: const TextStyle(
color: Colors.black,
),
todayDecoration: BoxDecoration( todayDecoration: BoxDecoration(
border: Border.all( border: Border.all(color: appTheme.blueA400, width: 2),
color: appTheme.blueA400,
width: 2,
),
color: Colors.white, color: Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
@@ -152,10 +116,7 @@ class _CalendarWidgetState extends State<CalendarWidget> {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.only(top: 8, bottom: 16), padding: const EdgeInsets.only(top: 8, bottom: 16),
child: Icon( child: Icon(_calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, color: Colors.grey.shade600),
_calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
color: Colors.grey.shade600,
),
), ),
); );
} }

View File

@@ -1,11 +1,11 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget { class FinishDayButtonWidget extends StatefulWidget {
final DiaryDayModel diaryDay; final DiaryDayEntity diaryDay;
const FinishDayButtonWidget({super.key, required this.diaryDay}); const FinishDayButtonWidget({super.key, required this.diaryDay});

View File

@@ -9,11 +9,7 @@ class GlossaryModerationRequestModalWidget extends StatefulWidget {
final DiaryCubit diaryCubit; final DiaryCubit diaryCubit;
final String productName; final String productName;
const GlossaryModerationRequestModalWidget({ const GlossaryModerationRequestModalWidget({super.key, required this.productName, required this.diaryCubit});
super.key,
required this.productName,
required this.diaryCubit,
});
@override @override
State<GlossaryModerationRequestModalWidget> createState() => _GlossaryModerationRequestModalWidgetState(); State<GlossaryModerationRequestModalWidget> createState() => _GlossaryModerationRequestModalWidgetState();
@@ -55,21 +51,13 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
const Text( const Text(
'Добавление нового продукта', 'Добавление нового продукта',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: Color(0xFF323F4B), fontSize: 20, fontWeight: FontWeight.w600),
color: Color(0xFF323F4B),
fontSize: 20,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( const Text(
'Оу... а у нас нет в базе калорийности такого продукта. Если вы знаете, давайте её запишем?', 'Оу... а у нас нет в базе калорийности такого продукта. Если вы знаете, давайте её запишем?',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: Color(0xFF323F4B), fontSize: 14, fontWeight: FontWeight.w400),
color: Color(0xFF323F4B),
fontSize: 14,
fontWeight: FontWeight.w400,
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
@@ -77,25 +65,27 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
children: [ children: [
Expanded( Expanded(
child: InkWell( child: InkWell(
onTap: () { onTap: () async {
Navigator.pop(context); glossary = (await widget.diaryCubit.sendProductToModeration(
name: productNameController.text,
energy: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
))?.glossary;
setState(() {
glossaryState = GlossaryState.success;
});
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(color: const Color(0xFFF6F6F6), borderRadius: BorderRadius.circular(20)),
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
alignment: Alignment.center, alignment: Alignment.center,
child: const Text( child: const Text(
'В другой раз', 'Отказаться',
style: TextStyle( style: TextStyle(color: Color(0xFF6D6F71), fontSize: 18, fontWeight: FontWeight.w500),
color: Color(0xFF6D6F71),
fontSize: 18,
fontWeight: FontWeight.w500,
),
), ),
), ),
), ),
@@ -111,20 +101,13 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
alignment: Alignment.center, alignment: Alignment.center,
child: const Text( child: const Text(
'Записать', 'Записать',
style: TextStyle( style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500),
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500,
),
), ),
), ),
), ),
@@ -146,18 +129,10 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
const Text( const Text(
'Продукт успешно отправлен\nна модерацию', 'Продукт успешно отправлен\nна модерацию',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: Color(0xFF323F4B), fontSize: 20, fontWeight: FontWeight.w600),
color: Color(0xFF323F4B),
fontSize: 20,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Lottie.asset( Lottie.asset('assets/lottie_animations/success.json', width: 160, height: 160),
'assets/lottie_animations/success.json',
width: 160,
height: 160,
),
const SizedBox(height: 16), const SizedBox(height: 16),
InkWell( InkWell(
onTap: () { onTap: () {
@@ -165,20 +140,13 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
alignment: Alignment.center, alignment: Alignment.center,
child: const Text( child: const Text(
'Закрыть', 'Закрыть',
style: TextStyle( style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500),
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500,
),
), ),
), ),
), ),
@@ -198,27 +166,16 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
const Text( const Text(
'Добавление нового продукта', 'Добавление нового продукта',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: Color(0xFF323F4B), fontSize: 20, fontWeight: FontWeight.w600),
color: Color(0xFF323F4B),
fontSize: 20,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( const Text(
'Если у вас данные по нему\nпросим заполнить значения на 100гр.', 'Если у вас данные по нему\nпросим заполнить значения на 100гр.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: Color(0xFF323F4B), fontSize: 14, fontWeight: FontWeight.w400),
color: Color(0xFF323F4B),
fontSize: 14,
fontWeight: FontWeight.w400,
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
CustomTextFormField( CustomTextFormField(hintText: 'Сырный суп', controller: productNameController),
hintText: 'Сырный суп',
controller: productNameController,
),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -245,11 +202,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
child: CustomTextFormField( child: CustomTextFormField(hintText: 'Жиры', controller: fatsController, keyboardType: const TextInputType.numberWithOptions(decimal: true)),
hintText: 'Жиры',
controller: fatsController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
@@ -270,8 +223,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
proteins: double.parse(proteinsController.text.replaceAll(',', '.')), proteins: double.parse(proteinsController.text.replaceAll(',', '.')),
fats: double.parse(fatsController.text.replaceAll(',', '.')), fats: double.parse(fatsController.text.replaceAll(',', '.')),
carbohydrates: double.parse(carbohydratesController.text.replaceAll(',', '.')), carbohydrates: double.parse(carbohydratesController.text.replaceAll(',', '.')),
)) ))?.glossary;
?.glossary;
setState(() { setState(() {
glossaryState = GlossaryState.success; glossaryState = GlossaryState.success;
}); });
@@ -279,20 +231,13 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
alignment: Alignment.center, alignment: Alignment.center,
child: const Text( child: const Text(
'Сохранить', 'Сохранить',
style: TextStyle( style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
), ),
), ),
), ),

View File

@@ -6,7 +6,7 @@ import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/generated/l10n.dart';
@@ -35,7 +35,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
diaryCubit = context.read<DiaryCubit>(); diaryCubit = context.read<DiaryCubit>();
} }
void showImagePicker(DiaryDayModel diaryDay) { void showImagePicker(DiaryDayEntity diaryDay) {
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
builder: (context) { builder: (context) {

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/create_day_model.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
@@ -69,14 +70,6 @@ class DiaryRepository {
} }
} }
Future<DiaryFutureDietDayModel> getDiaryByDateForDietOnly(String date) async {
try {
return (await diaryService.getDiaryIndexByDateForDietOnly(date: date)).last;
} catch (e) {
rethrow;
}
}
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async { Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
return (await diaryService.getDiaryIndexByDate(date: date)); return (await diaryService.getDiaryIndexByDate(date: date));
} }
@@ -164,7 +157,7 @@ class DiaryRepository {
required PortionState portionState, required PortionState portionState,
}) async { }) async {
Map<String, Object?> data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson(); Map<String, Object?> data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson();
data['product_nutritional_value_id'] = productNutritionalValueId; if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId;
data['state'] = portionState.name; data['state'] = portionState.name;
return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data); return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
} }
@@ -300,4 +293,69 @@ class DiaryRepository {
rethrow; rethrow;
} }
} }
Future<DiaryDayModel> getDiaryDayV2({required String day}) async {
try {
final diaryDay = await diaryService.getDiaryDayV2(day: day);
return setCPFCToDiary(diaryDay);
} catch (e) {
rethrow;
}
}
Future<void> createDiaryDay({required String day}) async {
try {
await diaryService.createDay(body: CreateDayModel(day: day));
await getDiaryByDate(day);
} catch (e) {
rethrow;
}
}
Future<DiaryDayModel> setCPFCToDiary(DiaryDayModel diaryDay) async {
Set<int> ids = {};
for (var meal in diaryDay.meals) {
for (var portion in meal.portions) {
if (portion.productNutritionalValueId != null) {
ids.add(portion.productNutritionalValueId!);
}
}
}
if (ids.isNotEmpty) {
final glossary = await getGlossariesIndex(ids: ids.toList());
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
final meal = updatedMeals[mealIndex];
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
}
}
return portion;
}).toList();
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
}
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
return updatedDiaryDay;
}
return diaryDay;
}
} }

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/create_day_model.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
@@ -17,8 +18,6 @@ abstract class DiaryService {
@GET('/api/diary/index/{date}') @GET('/api/diary/index/{date}')
Future<List<DiaryDayModel>> getDiaryIndexByDate({@Path('date') required String date}); Future<List<DiaryDayModel>> getDiaryIndexByDate({@Path('date') required String date});
@GET('/api/diary/index/{date}')
Future<List<DiaryFutureDietDayModel>> getDiaryIndexByDateForDietOnly({@Path('date') required String date});
@GET('/api/diary/{diary}/show') @GET('/api/diary/{diary}/show')
Future<DiaryDayWrapperModel> getDiaryDay({@Path('diary') required int diary}); Future<DiaryDayWrapperModel> getDiaryDay({@Path('diary') required int diary});
@@ -103,4 +102,10 @@ abstract class DiaryService {
@GET('/api/glossaries/index') @GET('/api/glossaries/index')
Future<GlossaryPaginationModel> getGlossariesIndex({@Queries() Map<String, dynamic>? filters}); Future<GlossaryPaginationModel> getGlossariesIndex({@Queries() Map<String, dynamic>? filters});
@POST('/api/v2/diary/create/day')
Future<void> createDay({@Body() required CreateDayModel body});
@GET('/api/v2/diary/index/{day?}')
Future<DiaryDayModel> getDiaryDayV2({@Query('day') required String day});
} }

View File

@@ -19,7 +19,7 @@ class AuthorCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
GoRouter.of(context).pushNamed('profileScreen', extra: author); GoRouter.of(context).pushNamed('profileScreen', extra: author.authorSlug);
}, },
child: Container( child: Container(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,

View File

@@ -8,7 +8,6 @@ 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/custom_popup.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart'; import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.dart';
@@ -60,14 +59,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: state.feedBlogPostList[widget.index].author.authorSlug,
authorSlug: state.feedBlogPostList[widget.index].author.authorSlug,
authorName: state.feedBlogPostList[widget.index].author.authorName,
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
? state.feedBlogPostList[widget.index].author.authorProfileIcon
: sl<AppConfig>().baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
),
); );
}, },
child: CircleAvatar( child: CircleAvatar(

View File

@@ -8,7 +8,6 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/prof
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/comment_model.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/core/comments/comments_cubit/comments_cubit.dart'; import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -96,12 +95,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: comment.authorSlug,
authorSlug: comment.authorSlug,
authorName: comment.authorName,
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : sl<AppConfig>().baseUrl + comment.authorProfileIcon,
authorIsMe: isMe,
),
); );
}, },
child: CircleAvatar( child: CircleAvatar(

View File

@@ -2,9 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart'; import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/profile/models/badge_model.dart'; import 'package:eatvio_app/features/profile/models/badge_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -22,12 +19,7 @@ class ConsultantWidget extends StatelessWidget {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: consultant.slug,
authorSlug: consultant.slug,
authorName: consultant.name,
authorProfileIcon: consultant.photo,
authorIsMe: (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == consultant.slug ? true : false,
),
); );
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),

View File

@@ -8,7 +8,6 @@ import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart'; import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -170,12 +169,7 @@ class _PlanWidgetState extends State<PlanWidget> {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: widget.plan.author.authorSlug,
authorSlug: widget.plan.author.authorSlug,
authorName: widget.plan.author.authorName,
authorProfileIcon: widget.plan.author.authorProfileIcon,
authorIsMe: widget.plan.author.authorIsMe,
),
); );
}, },
child: Padding( child: Padding(

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -21,13 +20,7 @@ class _BlockedUserWidgetState extends State<BlockedUserWidget> {
return InkWell( return InkWell(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: widget.profile.slug,
authorSlug: widget.profile.slug,
authorName: widget.profile.name,
authorProfileIcon: widget.profile.profilePhotoUrl,
authorIsMe: false,
isEntitySubscribedByMe: false,
),
), ),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),

View File

@@ -5,7 +5,6 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.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_model.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
@@ -24,9 +23,11 @@ class ProfileCubit extends Cubit<ProfileState> {
emit(const ProfileState.loading()); emit(const ProfileState.loading());
try { try {
final profile = await profileRepository.getProfileBySlug(slug); final profile = await profileRepository.getProfileBySlug(slug);
final myProfile = await profileRepository.getMyProfile();
emit( emit(
ProfileState.loaded( ProfileState.loaded(
profile: profile, profile: profile,
isMyProfile: myProfile.slug == profile.slug,
profileDetails: await profileRepository.getProfileDetailsBySlug(slug), profileDetails: await profileRepository.getProfileDetailsBySlug(slug),
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug), blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug),
consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null, consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null,
@@ -46,6 +47,7 @@ class ProfileCubit extends Cubit<ProfileState> {
emit( emit(
ProfileState.loaded( ProfileState.loaded(
profile: profile, profile: profile,
isMyProfile: true,
profileDetails: await profileRepository.getMyProfileDetails(), profileDetails: await profileRepository.getMyProfileDetails(),
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug), blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug),
weightPoints: await profileRepository.getWeight(period: period), weightPoints: await profileRepository.getWeight(period: period),
@@ -74,6 +76,7 @@ class ProfileCubit extends Cubit<ProfileState> {
weightPoints: (state as ProfileStateLoaded).weightPoints, weightPoints: (state as ProfileStateLoaded).weightPoints,
circumferencePoints: (state as ProfileStateLoaded).circumferencePoints, circumferencePoints: (state as ProfileStateLoaded).circumferencePoints,
consultantInfo: (state as ProfileStateLoaded).consultantInfo, consultantInfo: (state as ProfileStateLoaded).consultantInfo,
isMyProfile: (state as ProfileStateLoaded).isMyProfile,
), ),
); );
} }
@@ -100,11 +103,12 @@ class ProfileCubit extends Cubit<ProfileState> {
} }
} }
Future<String?> getChatRoomIdBySlug({required Author author}) async { Future<String?> getChatRoomIdBySlug({required String slug}) async {
try { try {
final chatIdOrError = await chatRepository.getChatByUserSlug(author.authorSlug); final chatIdOrError = await chatRepository.getChatByUserSlug(slug);
final userProfile = await profileRepository.getProfileBySlug(slug);
if (chatIdOrError == '404') { if (chatIdOrError == '404') {
AppRouter.navigatorKey.currentContext?.pushNamed('emptyChatScreen', extra: author); AppRouter.navigatorKey.currentContext?.pushNamed('emptyChatScreen', extra: userProfile);
} else if (chatIdOrError == 'error') { } else if (chatIdOrError == 'error') {
showErrorToast('Ошибка создания чата'); showErrorToast('Ошибка создания чата');
} else { } else {

View File

@@ -17,6 +17,7 @@ abstract class ProfileState with _$ProfileState {
required ProfileModel profile, required ProfileModel profile,
required ProfileDetailsModel profileDetails, required ProfileDetailsModel profileDetails,
required List<FeedBlogPostModel> blogPosts, required List<FeedBlogPostModel> blogPosts,
required bool isMyProfile,
List<WeightModel>? weightPoints, List<WeightModel>? weightPoints,
List<CircumferenceModel>? circumferencePoints, List<CircumferenceModel>? circumferencePoints,
ConsultationRequestModel? consultantInfo, ConsultationRequestModel? consultantInfo,

View File

@@ -13,7 +13,6 @@ import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/profile/models/circumference_model.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart';
@@ -44,9 +43,9 @@ import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class ProfileScreen extends StatefulWidget { class ProfileScreen extends StatefulWidget {
final Author? author; final String? slug;
const ProfileScreen({super.key, this.author}); const ProfileScreen({super.key, this.slug});
@override @override
State<ProfileScreen> createState() => _ProfileScreenState(); State<ProfileScreen> createState() => _ProfileScreenState();
@@ -71,7 +70,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug); widget.slug == null ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.slug!);
} }
@override @override
@@ -95,15 +94,37 @@ class _ProfileScreenState extends State<ProfileScreen> {
isFirstFetch = false; isFirstFetch = false;
} }
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state); // Проверяем, заблокировал ли пользователь меня
if (state.profile.isBlockedByUser) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.block, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'Пользователь ограничил доступ к своей странице',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[600]),
),
],
),
),
),
);
}
return state.isMyProfile ? _myProfileScreen(state) : _profileScreen(state);
} else if (state is ProfileStateError) { } else if (state is ProfileStateError) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: DataErrorWidget( body: DataErrorWidget(
onReload: () async { onReload: () async {
widget.author == null || widget.author!.authorIsMe widget.slug == null ? await profileCubit.getMyProfile() : await profileCubit.getProfileBySlug(widget.slug!);
? await profileCubit.getMyProfile()
: await profileCubit.getProfileBySlug(widget.author!.authorSlug);
}, },
), ),
); );
@@ -118,7 +139,17 @@ class _ProfileScreenState extends State<ProfileScreen> {
Scaffold _profileScreen(ProfileStateLoaded state) { Scaffold _profileScreen(ProfileStateLoaded state) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.grey.shade200,
appBar: AppBar(), appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -221,7 +252,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
Scaffold _myProfileScreen(ProfileStateLoaded state) { Scaffold _myProfileScreen(ProfileStateLoaded state) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.grey.shade200,
appBar: AppBar(toolbarHeight: widget.author == null ? 0 : null), appBar: AppBar(
toolbarHeight: widget.slug == null ? 0 : null,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -415,7 +457,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug); GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
}, },
child: Text( child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '', state.isMyProfile ? 'Редактировать' : '',
style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500), style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500),
), ),
), ),
@@ -493,7 +535,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug); GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
}, },
child: Text( child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '', state.isMyProfile ? 'Редактировать' : '',
style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500), style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500),
), ),
), ),
@@ -807,7 +849,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [sl<AppConfig>().baseUrl + state.profile.profilePhotoUrl], index: 0)); context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [sl<AppConfig>().baseUrl + state.profile.profilePhotoUrl], index: 0));
}, },
), ),
if (widget.author == null || widget.author!.authorIsMe) ...[ if (state.isMyProfile) ...[
_buildDivider(), _buildDivider(),
_buildCompactItem( _buildCompactItem(
icon: 'assets/icons/edit_paint.svg', icon: 'assets/icons/edit_paint.svg',
@@ -965,7 +1007,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
); );
}, },
), ),
if (widget.author != null) ...[ if (!state.isMyProfile) ...[
_buildDivider(), _buildDivider(),
_buildCompactItem( _buildCompactItem(
icon: 'assets/icons/complain.svg', icon: 'assets/icons/complain.svg',
@@ -974,12 +1016,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.profile, slug: widget.author?.authorSlug), builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.profile, slug: widget.slug),
); );
}, },
), ),
], ],
if (widget.author != null && !state.profile.isBlockedByMe) ...[ if (!state.isMyProfile && !state.profile.isBlockedByMe) ...[
_buildDivider(), _buildDivider(),
_buildCompactItem( _buildCompactItem(
icon: 'assets/icons/complain.svg', icon: 'assets/icons/complain.svg',
@@ -987,7 +1029,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
onTap: () { onTap: () {
isFirstFetch = true; isFirstFetch = true;
sl<BlockedUsersCubit>().blockUser(state.profile.slug); sl<BlockedUsersCubit>().blockUser(state.profile.slug);
widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug); state.isMyProfile ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(state.profile.slug);
if (state.profile.roles.contains('consultant')) { if (state.profile.roles.contains('consultant')) {
plansCubit.getPlans(state.profile.slug); plansCubit.getPlans(state.profile.slug);
} }
@@ -995,7 +1037,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}, },
), ),
], ],
if (widget.author != null && state.profile.isBlockedByMe) ...[ if (!state.isMyProfile && state.profile.isBlockedByMe) ...[
_buildDivider(), _buildDivider(),
_buildCompactItem( _buildCompactItem(
icon: 'assets/icons/complain.svg', icon: 'assets/icons/complain.svg',
@@ -1003,7 +1045,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
onTap: () { onTap: () {
isFirstFetch = true; isFirstFetch = true;
sl<BlockedUsersCubit>().unblockUser(state.profile.slug); sl<BlockedUsersCubit>().unblockUser(state.profile.slug);
widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug); state.isMyProfile ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(state.profile.slug);
if (state.profile.roles.contains('consultant')) { if (state.profile.roles.contains('consultant')) {
plansCubit.getPlans(state.profile.slug); plansCubit.getPlans(state.profile.slug);
} }
@@ -1066,7 +1108,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [ children: [
const SizedBox(width: 16), const SizedBox(width: 16),
GestureDetector( GestureDetector(
onTap: () async => await profileCubit.getChatRoomIdBySlug(author: widget.author!), onTap: () async => await profileCubit.getChatRoomIdBySlug(slug: widget.slug!),
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -15,13 +14,7 @@ class SubActionButtonWidget extends StatefulWidget {
final String name; final String name;
final String porfileIcon; final String porfileIcon;
const SubActionButtonWidget({ const SubActionButtonWidget({super.key, required this.subActionType, required this.slug, required this.name, required this.porfileIcon});
super.key,
required this.subActionType,
required this.slug,
required this.name,
required this.porfileIcon,
});
@override @override
State<SubActionButtonWidget> createState() => _SubActionButtonWidgetState(); State<SubActionButtonWidget> createState() => _SubActionButtonWidgetState();
@@ -49,51 +42,25 @@ class _SubActionButtonWidgetState extends State<SubActionButtonWidget> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: appTheme.blueA400),
borderRadius: BorderRadius.circular(8),
color: appTheme.blueA400,
),
child: const Text( child: const Text(
'Подписаться', 'Подписаться',
style: TextStyle( style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.white),
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.white,
),
), ),
), ),
); );
} else { } else {
return InkWell( return InkWell(
onTap: () async { onTap: () async {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed('chatScreen', extra: {'uuid': await sl<ProfileCubit>().getChatRoomIdBySlug(slug: widget.slug)});
'chatScreen',
extra: {
'uuid': await sl<ProfileCubit>().getChatRoomIdBySlug(
author: Author(
authorSlug: widget.slug,
authorName: widget.name,
authorProfileIcon: widget.porfileIcon,
authorIsMe: false,
),
),
},
);
}, },
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: const Color(0xFFF1F2F6)),
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFF1F2F6),
),
child: const Text( child: const Text(
'Сообщение', 'Сообщение',
style: TextStyle( style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF6D6F71)),
fontSize: 13,
fontWeight: FontWeight.w500,
color: Color(0xFF6D6F71),
),
), ),
), ),
); );

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/profile/models/sub_model.dart'; import 'package:eatvio_app/features/profile/models/sub_model.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -24,13 +23,7 @@ class _SubWidgetState extends State<SubWidget> {
return InkWell( return InkWell(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'profileScreen', 'profileScreen',
extra: Author( extra: widget.subModel.slug,
authorSlug: widget.subModel.slug,
authorName: widget.subModel.name,
authorProfileIcon: widget.subModel.profilePhotoUrl,
authorIsMe: false,
isEntitySubscribedByMe: false,
),
), ),
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),

View File

@@ -1,10 +1,12 @@
import 'package:eatvio_app/app_keys.dart'; import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
class WelcomeScreen extends StatefulWidget { class WelcomeScreen extends StatefulWidget {
@@ -82,8 +84,8 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
InkWell( InkWell(
key: AppKeys.welcomeScreenButton, key: AppKeys.welcomeScreenButton,
onTap: () async { onTap: () async {
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = sl<Prefs>();
await sharedPreferences.setBool('welcomeScreenWasShowed', true); await sharedPreferences.setBool(PrefsKeys.welcomeScreenWasShowed, true);
if (await TokenStorage.haveTokenInStorage()) { if (await TokenStorage.haveTokenInStorage()) {
goRouter.go('/feed-screen'); goRouter.go('/feed-screen');
} else { } else {