Compare commits

..

30 Commits

Author SHA1 Message Date
Username1548
96e80ced38 feat(consultantRating): added api requests and state to consultant rating page 2025-06-17 13:33:41 +07:00
mso7nueh
fa1192c5e5 Merge pull request 'upd ver -> 28' (#510) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#510
2025-06-16 11:33:55 +00:00
mso7nueh
41f948c8f7 upd ver -> 28 2025-06-16 14:33:32 +03:00
mso7nueh
896b2783ae Merge pull request 'feat(consultantFinances), feat(userPurchases)' (#494) from misha/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#494
2025-06-16 11:31:05 +00:00
mso7nueh
9c004f8593 Merge pull request 'fix(diary): fixed app review issues' (#502) from misha/eatvio_app:diaryFixes into main
Reviewed-on: eatvio/eatvio_app#502
2025-06-16 11:30:56 +00:00
mso7nueh
a577a8d58a Merge pull request 'fix(chat): time Issue-440' (#509) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#509
2025-06-16 11:25:29 +00:00
mso7nueh
3b2c4fdff5 fix(chat): time 2025-06-16 13:41:39 +03:00
mso7nueh
44d6487709 Merge pull request 'fix: testflight' (#507) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#507
2025-06-11 13:35:40 +00:00
mso7nueh
319fd2dfe3 fix: testflight 2025-06-11 16:35:21 +03:00
mso7nueh
a98043758b Merge pull request 'fix: testflight' (#506) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#506
2025-06-11 13:28:49 +00:00
mso7nueh
a84d2d7daa fix: testflight 2025-06-11 16:28:27 +03:00
Username1548
4acb861b69 merged upstram/main 2025-06-11 20:24:18 +07:00
Username1548
f163608a90 feat(permissions): added galery and camera permisshion checker 2025-06-11 20:13:26 +07:00
6bbec1f022 Merge pull request 'Issue-440 & Issue-194 & build 27' (#503) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#503
2025-06-11 12:49:09 +00:00
mso7nueh
c0311bf213 feat(chat&notifications): Issue-191 and Issue-440 and auth screen policy 2025-06-11 15:34:31 +03:00
mso7nueh
9d1e621769 feat(chat&notifications): Issue-191 and Issue-440 and auth screen policy 2025-06-11 15:34:16 +03:00
mso7nueh
11b695cefd feat(dev-tools): Issue-441 2025-06-10 17:04:29 +03:00
Username1548
926f0de450 feat(userPurchases, consultantFinances): added status to models and layout for status 2025-06-10 19:57:20 +07:00
mso7nueh
dc81085ae4 fix(chat&reg): fix chat screen, reg email valid 2025-06-09 17:57:10 +03:00
mso7nueh
8c0efc1a34 fix(chat&reg): fix chat screen, reg email valid 2025-06-09 17:57:01 +03:00
Username1548
483cf6d8ee feat(userPurchases): added user purchases, fix(consultations): fixed consultations bug 2025-06-09 14:01:05 +07:00
mso7nueh
87d3f058f6 Merge pull request 'Issue-329' (#500) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#500
2025-06-06 12:41:37 +00:00
mso7nueh
7a3c6e6b07 Merge remote-tracking branch 'origin/main' 2025-06-06 15:40:37 +03:00
mso7nueh
e6bad9d35d fix(blogPost): Issue-329 2025-06-06 15:40:27 +03:00
mso7nueh
72cf203658 Merge pull request 'build 26 + 1.1.3+33' (#495) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#495
2025-06-06 08:33:16 +00:00
Username1548
086fd1cac4 Merge remote-tracking branch 'upstream/main' 2025-06-05 22:43:50 +07:00
88e33765d3 Merge pull request 'feat(chats): added refresh timer for chat room, feat(diary): fixed plan duration in plan widget and added date to app bar)' (#492) from misha/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#492
2025-06-05 15:43:21 +00:00
Username1548
6fa4bffd7e feat(consultantFinaces): added consultant finaces screen 2025-06-05 22:42:50 +07:00
mso7nueh
70620d8d9e fix(blogPost): update without image 2025-06-05 17:36:49 +03:00
mso7nueh
0b2464ea19 fix(blogPost): update without image 2025-06-05 17:36:15 +03:00
58 changed files with 1678 additions and 785 deletions

View File

@@ -40,6 +40,17 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.camera
'PERMISSION_CAMERA=1',
## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',
'PERMISSION_NOTIFICATIONS=1',
## dart: PermissionGroup.mediaLibrary
'PERMISSION_MEDIA_LIBRARY=1',
]
end
end
end

View File

@@ -388,7 +388,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 24;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -538,7 +538,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 24;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -576,7 +576,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 24;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>24</string>
<string>27</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@@ -32,11 +32,11 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Нужно для загрузки фото</string>
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Нужно для загрузки фото</string>
<string>Предоставление доступа позволит Вам добавлять фото к записям</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>

View File

@@ -1,5 +1,9 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
@@ -16,6 +20,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
@@ -154,6 +160,23 @@ class AppRouter {
name: 'welcomeScreen',
builder: (context, state) => const WelcomeScreen(),
redirect: (context, state) async {
try {
if (await TokenStorage.haveTokenInStorage()) {
final firebaseApi = sl<FirebaseApi>();
final fbToken = await firebaseApi.getToken();
final deviceInfo = DeviceInfoPlugin();
await sl<Dio>().post(
'/api/profile/notification/token/register',
data: {
'platform': Platform.isIOS ? 'ios': 'android',
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
'token': fbToken,
},
);
}
} catch (e) {
rethrow;
}
final sharedPreferences = await SharedPreferences.getInstance();
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed');
if (welcomeScreenWasShowed == true) {
@@ -181,10 +204,20 @@ class AppRouter {
},
),
GoRoute(
path: '/consultant-finance-screen',
name: 'consultantFinanceScreen',
path: ConsultationsArchiveScreen.path,
name: ConsultationsArchiveScreen.name,
builder: (context, state) => const ConsultationsArchiveScreen(),
),
GoRoute(
path: ConsultantFinancesScreen.path,
name: ConsultantFinancesScreen.name,
builder: (context, state) => const ConsultantFinancesScreen(),
),
GoRoute(
path: UserPurchasesScreen.path,
name: UserPurchasesScreen.name,
builder: (context, state) => const UserPurchasesScreen(),
),
GoRoute(
path: '/user-finance-screen',
name: 'userFinanceScreen',

View File

@@ -1,11 +1,10 @@
import 'package:firebase_messaging/firebase_messaging.dart';
class FirebaseApi {
final _firebaseMessaging = FirebaseMessaging.instance;
Future<String?> getToken() async{
Future<String?> getToken() async {
await _firebaseMessaging.requestPermission();
return await _firebaseMessaging.getToken();
}
}
}

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
@@ -13,6 +14,9 @@ import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
@@ -30,6 +34,8 @@ import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
@@ -153,6 +159,9 @@ Future<void> init() async {
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
@@ -181,6 +190,7 @@ Future<void> init() async {
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
@@ -209,7 +219,9 @@ Future<void> init() async {
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
// External
sl.registerLazySingleton(

View File

@@ -0,0 +1,23 @@
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:permission_handler/permission_handler.dart';
class PermissionHandler {
static Future<void> checkPermission({
required Permission permission,
required String title,
required String description,
required Future<void> Function() onGranted,
required BuildContext context,
}) async {
final permissionState = await permission.request();
if (permissionState.isGranted) {
await onGranted();
return;
}
if (context.mounted) {
showErrorDialog(context, title: title, description: description, buttonTitle: S.of(context).allowAccess, needToOpenSettings: true);
}
}
}

View File

@@ -8,31 +8,39 @@ extension DateTimeExtension on DateTime {
String pattern = dateTimeFormatPattern,
String? locale,
]) {
final localDate = isUtc ? toLocal() : this;
if (locale != null && locale.isNotEmpty) {
initializeDateFormatting(locale);
}
return DateFormat(pattern, locale).format(this);
return DateFormat(pattern, locale).format(localDate);
}
bool isYesterday(DateTime date) {
final DateTime localDate = date.isUtc ? date.toLocal() : date;
final yesterday = DateTime.now().subtract(const Duration(days: 1));
return yesterday.day == localDate.day && yesterday.month == localDate.month && yesterday.year == localDate.year;
}
bool isToday(DateTime date) {
final DateTime localDate = date.isUtc ? date.toLocal() : date;
bool isYesterday() {
final localDate = isUtc ? toLocal() : this;
final now = DateTime.now();
return now.day == localDate.day && now.month == localDate.month && now.year == localDate.year;
final yesterday = DateTime(now.year, now.month, now.day).subtract(const Duration(days: 1));
return localDate.year == yesterday.year &&
localDate.month == yesterday.month &&
localDate.day == yesterday.day;
}
bool isToday() {
final localDate = isUtc ? toLocal() : this;
final now = DateTime.now();
return now.year == localDate.year &&
now.month == localDate.month &&
now.day == localDate.day;
}
}
String calculateDifference(DateTime dateTime) {
if (dateTime.isToday(dateTime)) {
final localDateTime = dateTime.isUtc ? dateTime.toLocal() : dateTime;
if (localDateTime.isToday()) {
return 'Сегодня';
} else if (dateTime.isYesterday(dateTime)) {
} else if (localDateTime.isYesterday()) {
return 'Вчера';
} else {
return dateTime.format('d MMMM', 'ru');
return localDateTime.format('d MMMM', 'ru');
}
}

View File

@@ -4,8 +4,9 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:permission_handler/permission_handler.dart';
Future<void> showErrorDialog(BuildContext context, {String? title, String? description}) async {
Future<void> showErrorDialog(BuildContext context, {String? title, String? description, String? buttonTitle, bool? needToOpenSettings}) async {
return await showDialog<void>(
context: context,
barrierColor: Colors.transparent,
@@ -43,8 +44,13 @@ Future<void> showErrorDialog(BuildContext context, {String? title, String? descr
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => AppRouter.navigatorKey.currentContext?.pop(),
child: Text(s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
onPressed: () {
AppRouter.navigatorKey.currentContext?.pop();
if (needToOpenSettings ?? false) {
openAppSettings();
}
},
child: Text(buttonTitle ?? s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
),
const SizedBox(height: 12),

View File

@@ -1,6 +1,4 @@
import 'dart:async';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -9,8 +7,10 @@ import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.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/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
@@ -23,8 +23,6 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../bloc/auth_state.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@@ -41,6 +39,7 @@ class _AuthScreenState extends State<AuthScreen> {
late StreamSubscription<bool> keyboardSubscription;
bool isFocused = false;
int tapsCount = 0;
@override
void initState() {
super.initState();
@@ -83,11 +82,8 @@ class _AuthScreenState extends State<AuthScreen> {
tapsCount++;
if (tapsCount == 6) {
tapsCount = 0;
// showModalBottomSheet(context: context, builder: (context) => );
}
EasyDebounce.debounce('change env', Durations.medium4, () {
tapsCount = 0;
});
GoRouter.of(context).pushNamed(DevToolsScreen.name);
},
child: SvgPicture.asset(
ImageConstant.imgLogoEatvio,
@@ -153,7 +149,7 @@ class _AuthScreenState extends State<AuthScreen> {
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'Нажимая "Войти", Вы соглашаетесь \n',
text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n',
style: const TextStyle(
fontSize: 12,
color: Colors.black,
@@ -170,6 +166,17 @@ class _AuthScreenState extends State<AuthScreen> {
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
),
const TextSpan(text: ' и '),
TextSpan(
text: 'Политикой конфиденциальности\nи защитой персональных данных',
style: TextStyle(
fontSize: 12,
color: appTheme.blueA400,
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
),
],
),
),

View File

@@ -215,7 +215,10 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
child: Html(data: widget.blogPost.description, style: {"*": Style(color: Colors.black87)}),
),
const SizedBox(height: 8),
CachedNetworkImage(imageUrl: '$baseUrl/storage/${widget.blogPost.image}'),
if (widget.blogPost.image.isNotEmpty)
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.blogPost.image}',
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(

View File

@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/app_decoration.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
@@ -17,6 +19,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class BlogPostCreateScreen extends StatefulWidget {
final FeedBlogPostModel? feedBlogPostModel;
@@ -80,47 +83,65 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
_showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
builder: (context) {
final s = S.of(context);
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (!context.mounted) return;
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
},
);
}

View File

@@ -73,7 +73,7 @@ class BlogPostRepository {
'video_url': videoUrl,
'image': image != null
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
: oldImageUrl != null
: oldImageUrl != '' && oldImageUrl != null
? await MultipartFile.fromFile((await downloadImage(oldImageUrl))?.path ?? '', filename: oldImageUrl.split('/').last)
: null,
});

View File

@@ -9,7 +9,6 @@ abstract class ChatUserModel with _$ChatUserModel {
required int id,
required String name,
required String slug,
@JsonKey(name: 'profile_photo_path') String? profilePhotoPath,
@JsonKey(name: 'profile_photo_url') String? profilePhotoUrl,
}) = _ChatUserModel;

View File

@@ -2,7 +2,6 @@ import 'dart:io';
import 'dart:math';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
@@ -155,7 +154,7 @@ class _ChatScreenState extends State<ChatScreen> {
if (state.chatRoom.users.receiver.profilePhotoUrl != null)
CircleAvatar(
radius: 20,
foregroundImage: NetworkImage(baseUrl + state.chatRoom.users.receiver.profilePhotoUrl!),
foregroundImage: NetworkImage(state.chatRoom.users.receiver.profilePhotoUrl!),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
if (state.chatRoom.users.receiver.profilePhotoUrl == null)

View File

@@ -1,6 +1,5 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:eatvio_app/core/const.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/utils/date_time_utils.dart';
@@ -40,7 +39,7 @@ class ChatBubbleWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(right: 4, bottom: 4),
child: Text(
sendTime.format('hh:mm'),
sendTime.format('HH:mm', 'ru'),
style: const TextStyle(
color: Color(0xFF7B8794),
height: 1,
@@ -81,7 +80,7 @@ class ChatBubbleWidget extends StatelessWidget {
contentPadding: const EdgeInsets.all(4),
horizontalTitleGap: 8,
onTap: () async {
if (!await launchUrl(Uri.parse('$baseUrl/storage/${mediaData[index]}'))) {
if (!await launchUrl(Uri.parse('${mediaData[index]}'))) {
throw Exception('Could not launch ${mediaData[index].toString()}');
}
},
@@ -108,18 +107,18 @@ class ChatBubbleWidget extends StatelessWidget {
onTap: () {
context.pushTransparentRoute(
PhotoViewScreen(
imageUrls: mediaData.map((e) => '$baseUrl/storage/$e').toList(),
imageUrls: mediaData.map((e) => '$e').toList(),
index: index,
),
);
},
child: Hero(
tag: '$baseUrl/storage/${mediaData[index]}',
tag: '${mediaData[index]}',
transitionOnUserGestures: true,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${mediaData[index]}',
imageUrl: '${mediaData[index]}',
),
),
),
@@ -145,7 +144,7 @@ class ChatBubbleWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 4),
child: Text(
sendTime.format('hh:mm'),
sendTime.format('HH:mm', 'ru'),
style: const TextStyle(
color: Color(0xFF7B8794),
height: 1,

View File

@@ -1,4 +1,3 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
@@ -42,7 +41,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
CircleAvatar(
radius: 24,
foregroundImage: NetworkImage(
baseUrl + widget.chatRoom.users.toChatUser.profilePhotoUrl,
widget.chatRoom.users.toChatUser.profilePhotoUrl,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
backgroundColor: Colors.white,

View File

@@ -1,16 +1,42 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'rating_model.freezed.dart';
part 'rating_model.g.dart';
@freezed
abstract class RatingModel with _$RatingModel {
abstract class RatingPaginationModel with _$RatingPaginationModel {
factory RatingPaginationModel({
required List<RatingModel> data,
}) = _RatingPaginationModel;
factory RatingPaginationModel.fromJson(Map<String, dynamic> json) => _$RatingPaginationModelFromJson(json);
}
@freezed
abstract class RatingModel with _$RatingModel {
factory RatingModel({
required DateTime date,
required String title,
required int amount,
@JsonKey(name: 'created_at', fromJson: parseDate) required DateTime date,
@JsonKey(name: 'log_data') required RatingEnum type,
@JsonKey(name: 'log_scores') required int amount,
}) = _RatingModel;
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
}
}
enum RatingEnum {
@JsonValue('ADDED_RECIPE')
addedRecipe,
@JsonValue('ADDED_PUBLIC_DIET')
addedPublicDiet,
@JsonValue('ADDED_BLOG_POST')
addedBlogPost,
@JsonValue('INVITED_A_NEW_USER')
invitedUser,
@JsonValue('HAS_MEDICAL_DEGREE')
hasMedicalDegree,
@JsonValue('CONSULTED')
consulted,
@JsonValue('PRODUCT_NUTRITINAL_VALUE_APPROVED')
addedGlossary;
}

View File

@@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'statistics_model.freezed.dart';
part 'statistics_model.g.dart';
@freezed
abstract class StatisticsModel with _$StatisticsModel {
factory StatisticsModel({
@JsonKey(name: 'count_referal') int? countReferal,
@JsonKey(name: 'count_blog_posts') int? blogPostsCount,
@JsonKey(name: 'count_recipes') int? recipesCount,
@JsonKey(name: 'count_diets') int? dietsCount,
@JsonKey(name: 'count_consultants') int? consultationsCount,
@JsonKey(name: 'count_products_nutritional_values') int? qwe,
}) = _StatisticsModel;
factory StatisticsModel.fromJson(Map<String, dynamic> json) => _$StatisticsModelFromJson(json);
}

View File

@@ -1,12 +1,16 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultantRatingScreen extends StatelessWidget {
static const name = 'consultantRatingScreen';
@@ -15,7 +19,10 @@ class ConsultantRatingScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const ConsultantRatingPageContent();
return BlocProvider<ConsultantRatingCubit>(
create: (context) => sl<ConsultantRatingCubit>()..getConsultantStatistics()..getScoreLogs(),
child: const ConsultantRatingPageContent(),
);
}
}
@@ -25,52 +32,56 @@ class ConsultantRatingPageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
centerTitle: true,
title: Text(
s.rating_and_statistics,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 16),
const RatingWidget(
rating: 1058,
return BlocBuilder<ConsultantRatingCubit, ConsultantRatingState>(
builder: (context, state) {
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
centerTitle: true,
title: Text(
s.rating_and_statistics,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CustomOutlinedButton(
text: s.copy_referral_link,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
body: state.error != null
? DataErrorWidget(onReload: () async {
await context.read<ConsultantRatingCubit>().getConsultantStatistics();
if (!context.mounted) return;
await context.read<ConsultantRatingCubit>().getScoreLogs();
})
: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 16),
const RatingWidget(
rating: 1058,
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CustomOutlinedButton(
text: s.copy_referral_link,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () {},
),
),
const SizedBox(height: 16),
StatisticWidget(isLoading: state.isStatisticsLoading, statistics: state.statistics,),
const SizedBox(height: 16),
RatingHistoryWidget(
isLoading: state.isRatingLoading,
ratingHistory: state.ratings,
),
],
),
),
onPressed: () {},
),
),
const SizedBox(height: 16),
const StatisticWidget(),
const SizedBox(height: 16),
RatingHistoryWidget(
ratingHistory: [
RatingModel(date: DateTime.now(), title: ' djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 201300),
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 89323),
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 20),
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 1212),
RatingModel(date: DateTime.now(), title: 'qweqweeq', amount: 323),
RatingModel(date: DateTime.now(), title: 'ee', amount: 1),
],
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,32 @@
import 'dart:developer';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
final ConsultantRatingRepository repository;
ConsultantRatingCubit(this.repository) : super(ConsultantRatingState());
Future<void> getConsultantStatistics({String? date}) async {
try {
emit(state.copyWith(isStatisticsLoading: true));
final statistics = await repository.getConsultantStatistics(date: date);
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null));
} catch (e, st) {
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
log(e.toString(), stackTrace: st);
}
}
Future<void> getScoreLogs({String? date}) async {
try {
emit(state.copyWith(isRatingLoading: true));
final scoreLogs = await repository.getScoreLogs(date: date);
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
} catch (e, st) {
emit(state.copyWith(isRatingLoading: true, error: e.toString()));
log(e.toString(), stackTrace: st);
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'consultant_rating_state.freezed.dart';
@freezed
abstract class ConsultantRatingState with _$ConsultantRatingState {
factory ConsultantRatingState({
@Default(false) bool isStatisticsLoading,
@Default(false) bool isRatingLoading,
@Default(null) StatisticsModel? statistics,
@Default(null) String? error,
@Default(<RatingModel>[]) List<RatingModel> ratings,
}) = _ConsultantRatingState;
}

View File

@@ -1,42 +1,29 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class PeriodFilterWidget extends StatefulWidget {
const PeriodFilterWidget({super.key});
const PeriodFilterWidget({super.key, required this.isStatistic});
final bool isStatistic;
@override
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
}
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
final List<String> periodDropDownTitles = [];
int periodDropDownIndex = 0;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final s = S.of(context);
if (periodDropDownTitles.isEmpty) {
periodDropDownTitles.addAll([
s.for_whole_period_of_time,
s.for_a_year,
s.for_a_month,
s.for_a_week,
]);
}
}
PeriodFilterStatus _currentState = PeriodFilterStatus.all;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomDropDownButton(
CustomDropDownButton<PeriodFilterStatus>(
hint: Text(
s.choose_diet_duration,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
@@ -44,24 +31,59 @@ class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
onChanged: (value) {
if (value != null) {
setState(() {
periodDropDownIndex = periodDropDownTitles.indexOf(value);
_currentState = value;
});
if (widget.isStatistic) {
if (value == PeriodFilterStatus.all) {
context.read<ConsultantRatingCubit>().getConsultantStatistics();
return;
}
context.read<ConsultantRatingCubit>().getConsultantStatistics(date: value.value);
return;
}
if (value == PeriodFilterStatus.all) {
context.read<ConsultantRatingCubit>().getScoreLogs();
return;
}
context.read<ConsultantRatingCubit>().getScoreLogs(date: value.value);
return;
}
},
items: periodDropDownTitles
items: PeriodFilterStatus.values
.mapIndexed(
(index, value) => DropdownMenuItem(
value: periodDropDownTitles[index],
value: PeriodFilterStatus.values[index],
child: Text(
periodDropDownTitles[index],
PeriodFilterStatus.values[index].name,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
)
.toList(),
value: periodDropDownTitles[periodDropDownIndex],
value: _currentState,
),
],
);
}
}
enum PeriodFilterStatus {
all,
month,
week,
year;
String get name => switch (this) {
PeriodFilterStatus.all => 'Все периоды',
PeriodFilterStatus.month => 'За месяц',
PeriodFilterStatus.week => 'За неделю',
PeriodFilterStatus.year => 'За год',
};
String? get value => switch (this) {
PeriodFilterStatus.all => null,
PeriodFilterStatus.month => DateTime.now().subtract(const Duration(days: 30)).format('yyyy-MM-dd'),
PeriodFilterStatus.week => DateTime.now().subtract(const Duration(days: 7)).format('yyyy-MM-dd'),
PeriodFilterStatus.year => DateTime.now().subtract(const Duration(days: 365)).format('yyyy-MM-dd'),
};
}

View File

@@ -11,7 +11,6 @@ class RatingHistoryItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 80,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: appTheme.gray100,
@@ -29,7 +28,7 @@ class RatingHistoryItem extends StatelessWidget {
date.format('dd.MM.yyyy'),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
),
const Spacer(),
const SizedBox(height: 12),
Text(
title,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
@@ -9,7 +10,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class RatingHistoryWidget extends StatelessWidget {
const RatingHistoryWidget({super.key, required this.ratingHistory});
const RatingHistoryWidget({super.key, required this.ratingHistory, required this.isLoading});
final bool isLoading;
final List<RatingModel> ratingHistory;
@override
Widget build(BuildContext context) {
@@ -20,50 +22,60 @@ class RatingHistoryWidget extends StatelessWidget {
right: 16,
top: 16,
),
child: Column(
children: [
Row(
children: [
Text(
s.rating_history,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const Spacer(),
SvgPicture.asset(
'assets/icons/question_mark.svg',
height: 22,
width: 22,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
child: isLoading
? const Center(child: Loader())
: Column(
children: [
Row(
children: [
Text(
s.rating_history,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const Spacer(),
SvgPicture.asset(
'assets/icons/question_mark.svg',
height: 22,
width: 22,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
],
),
),
],
),
const SizedBox(
height: 16,
),
const PeriodFilterWidget(),
const SizedBox(
height: 12,
),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Padding(
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
child: RatingHistoryItem(
title: ratingHistory[index].title,
amount: ratingHistory[index].amount,
date: ratingHistory[index].date,
const SizedBox(
height: 16,
),
);
},
itemCount: ratingHistory.length,
shrinkWrap: true,
),
],
),
const PeriodFilterWidget(isStatistic: false),
const SizedBox(
height: 12,
),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Padding(
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
child: RatingHistoryItem(
title: switch (ratingHistory[index].type) {
RatingEnum.addedRecipe => 'Добавление рецепта',
RatingEnum.addedPublicDiet => 'Добавление публичной диеты',
RatingEnum.addedBlogPost => 'Добавление поста',
RatingEnum.invitedUser => 'Регистрация по реф. ссылке',
RatingEnum.hasMedicalDegree => 'Получение мед. степени',
RatingEnum.consulted => 'Консультация',
RatingEnum.addedGlossary => 'Добавление продукта в глоссарий',
},
amount: ratingHistory[index].amount,
date: ratingHistory[index].date,
),
);
},
itemCount: ratingHistory.length,
shrinkWrap: true,
),
],
),
);
}
}

View File

@@ -1,6 +1,8 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -8,14 +10,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class StatisticWidget extends StatelessWidget {
const StatisticWidget({super.key});
const StatisticWidget({super.key, required this.isLoading, required this.statistics});
final bool isLoading;
final StatisticsModel? statistics;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MainBackgroundBouble(
padding: const EdgeInsets.all(16),
child: Column(
child: isLoading ? const Center(child: Loader()) : Column(
children: [
Row(
children: [
@@ -38,15 +41,17 @@ class StatisticWidget extends StatelessWidget {
const SizedBox(
height: 16,
),
const PeriodFilterWidget(),
const PeriodFilterWidget(
isStatistic: true,
),
const SizedBox(
height: 12,
),
Row(
children: [
Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)),
Expanded(child: StatisticBouble(number: statistics?.countReferal ?? 0, title: s.invited_people_count)),
const SizedBox(width: 8),
Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)),
Expanded(child: StatisticBouble(number: statistics?.blogPostsCount ?? 0, title: s.published_posts_count)),
],
),
const SizedBox(
@@ -54,9 +59,9 @@ class StatisticWidget extends StatelessWidget {
),
Row(
children: [
Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)),
Expanded(child: StatisticBouble(number: statistics?.recipesCount ?? 0, title: s.published_recipes_count)),
const SizedBox(width: 8),
Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)),
Expanded(child: StatisticBouble(number: statistics?.dietsCount ?? 0, title: s.published_diets_count)),
],
),
const SizedBox(
@@ -64,9 +69,9 @@ class StatisticWidget extends StatelessWidget {
),
Row(
children: [
Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)),
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
const SizedBox(width: 8),
Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)),
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
],
),
const SizedBox(

View File

@@ -0,0 +1,26 @@
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
class ConsultantRatingRepository {
final ConsultantRatingService service;
ConsultantRatingRepository(this.service);
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
try {
return await service.getConsultantStatistics(date: date);
} catch (e) {
rethrow;
}
}
Future<List<RatingModel>> getScoreLogs({String? date}) async {
try {
final data = (await service.getScoreLogs(date: date)).data;
return data;
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,18 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
import 'package:retrofit/retrofit.dart';
part 'consultant_rating_service.g.dart';
@RestApi()
abstract class ConsultantRatingService {
factory ConsultantRatingService(Dio dio, {String baseUrl}) = _ConsultantRatingService;
@GET('/api/consultants/rating/statistics')
Future<StatisticsModel> getConsultantStatistics({@Query('filters[duration][\$gte]') String? date});
@GET('/api/consultants/score/logs')
Future<RatingPaginationModel> getScoreLogs({@Query('filters[duration][\$gte]') String? date});
}

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -67,7 +68,7 @@ class ConsultationsContent extends StatelessWidget {
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
GoRouter.of(context).pushNamed('consultantFinanceScreen');
GoRouter.of(context).pushNamed(ConsultationsArchiveScreen.name);
},
child: Ink(
width: MediaQuery.of(context).size.width - 32,

View File

@@ -185,6 +185,8 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
children: List.generate(widget.consultationsModel.duration, (index) {
final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
final canConsultantOpenThisDay = day != null &&
(day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted);
return Padding(
padding: const EdgeInsets.only(right: 12),
child: Column(
@@ -192,7 +194,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
children: [
InkWell(
onTap: () {
if (!hasActivity) {
if (!hasActivity || !canConsultantOpenThisDay) {
showErrorDialog(context,
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
return;

View File

@@ -1,7 +1,9 @@
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
class ConsultationsRepository {
final ConsultationService consultationService;
@@ -42,4 +44,20 @@ class ConsultationsRepository {
rethrow;
}
}
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
try {
return await consultationService.getConsultantFinances(date, dateComparison);
} catch (e) {
rethrow;
}
}
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
try {
return await consultationService.getUserPurchases(date, dateComparison);
} catch (e) {
rethrow;
}
}
}

View File

@@ -1,7 +1,9 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
class ConsultationService {
final Dio dio;
@@ -66,4 +68,42 @@ class ConsultationService {
rethrow;
}
}
Future<List<ConsultantFinanceModel>> getConsultantFinances(
String? date,
FiltersComparisonEnum? dateComparison,
) async {
try {
final response = await dio.get(
'/api/finances/my_finances',
queryParameters: {
if (dateComparison != null && date != null) 'filters[end_date]': date,
},
);
final data = response.data as Map<String, dynamic>;
final consultations = data['data'] as List<dynamic>;
return consultations.map((consultation) => ConsultantFinanceModel.fromJson(consultation as Map<String, dynamic>)).toList();
} catch (e) {
rethrow;
}
}
Future<List<UserPurchasesModel>> getUserPurchases(
String? date,
FiltersComparisonEnum? dateComparison,
) async {
try {
final response = await dio.get(
'/api/finances/my_purchases',
queryParameters: {
if (dateComparison != null && date != null) 'filters[start_date]': date,
},
);
final data = response.data as Map<String, dynamic>;
final purchases = data['data'] as List<dynamic>;
return purchases.map((purchase) => UserPurchasesModel.fromJson(purchase as Map<String, dynamic>)).toList();
} catch (e) {
rethrow;
}
}
}

View File

@@ -1,6 +1,8 @@
import 'dart:io';
import 'package:dotted_border/dotted_border.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
@@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class DietImagePickerWidget extends StatefulWidget {
const DietImagePickerWidget({super.key});
@@ -28,11 +32,18 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
cubit.setImage(image);
if (!context.mounted) return;
Navigator.of(context).pop();
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
cubit.setImage(image);
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: Row(
children: [
@@ -44,11 +55,18 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
cubit.setImage(image);
if (!context.mounted) return;
Navigator.of(context).pop();
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
cubit.setImage(image);
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: Row(
children: [

View File

@@ -1,6 +1,8 @@
import 'dart:io';
import 'package:dotted_border/dotted_border.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
@@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class PlanImagePickerWidget extends StatefulWidget {
const PlanImagePickerWidget({super.key});
@@ -28,11 +32,18 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
cubit.setImage(image);
if (!context.mounted) return;
Navigator.of(context).pop();
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
cubit.setImage(image);
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: Row(
children: [
@@ -44,11 +55,18 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
cubit.setImage(image);
if (!context.mounted) return;
Navigator.of(context).pop();
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
cubit.setImage(image);
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: Row(
children: [

View File

@@ -1,11 +1,16 @@
import 'dart:io';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class ImageWidget extends StatefulWidget {
const ImageWidget({super.key, required this.image});
@@ -67,55 +72,72 @@ class _ImageWidgetState extends State<ImageWidget> {
_showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
builder: (context) {
final s = S.of(context);
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (image != null) {
createRecipeCubit.replaceImage(File(image.path));
createRecipeCubit.validateImage();
}
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
},
);
}
}

View File

@@ -1,10 +1,15 @@
import 'dart:io';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class StepImageWidget extends StatefulWidget {
final CreateRecipeCubit createRecipeCubit;
@@ -12,7 +17,8 @@ class StepImageWidget extends StatefulWidget {
const StepImageWidget({
super.key,
required this.index, required this.createRecipeCubit,
required this.index,
required this.createRecipeCubit,
});
@override
@@ -79,53 +85,70 @@ class _StepImageWidgetState extends State<StepImageWidget> {
_showImagePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (image != null) {
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
}
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
builder: (context) {
final s = S.of(context);
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.camera);
setState(() {});
if (image != null) {
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
}
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (image != null) {
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
}
if (!context.mounted) return;
Navigator.of(context).pop();
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
final picker = ImagePicker();
image = await picker.pickImage(source: ImageSource.gallery);
setState(() {});
if (image != null) {
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
}
AppRouter.navigatorKey.currentContext?.pop();
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
},
);
}
}

View File

@@ -1,16 +1,21 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.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/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class PhotoGalleryWidget extends StatefulWidget {
const PhotoGalleryWidget({super.key});
@@ -32,55 +37,75 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
diaryCubit.uploadPhotos(
files: [File(image.path)],
diaryId: diaryDay.id,
builder: (context) {
final s = S.of(context);
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
AppRouter.navigatorKey.currentContext?.pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
diaryCubit.uploadPhotos(
files: [File(image.path)],
diaryId: diaryDay.id,
);
}
},
context: context,
);
}
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
),
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty) {
diaryCubit.uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
AppRouter.navigatorKey.currentContext?.pop();
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty) {
diaryCubit.uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
);
}
},
context: context,
);
}
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
},
);
}
@@ -92,7 +117,9 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
int mealIndex = state.selectedMeal - 1;
if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) {
return GestureDetector(
onTap: () => showImagePicker(state.diary),
onTap: () async {
showImagePicker(state.diary);
},
child: SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/no_one_image_was_added.svg',

View File

@@ -20,7 +20,7 @@ abstract class DietsPagination with _$DietsPagination {
abstract class Diet with _$Diet {
const factory Diet({
@JsonKey(name: "id") required int id,
@JsonKey(name: "slug") String? slug,
@JsonKey(name: "slug") required String slug,
@JsonKey(name: "name") required String name,
@JsonKey(name: "image") required String image,
@JsonKey(name: "duration") required int duration,

View File

@@ -1,5 +1,6 @@
import 'dart:developer';
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
@@ -13,19 +14,22 @@ class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
ConsultantFinancesState(),
);
Future<void> getConsultations() async {
Future<void> getConsultantFinances() async {
try {
emit(state.copyWith(isLoading: true));
final consultantFinances = await consultationsRepository.getConsultantFinances(
state.periodFilterStatus?.value,
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
);
emit(state.copyWith(financesModels: consultantFinances, isLoading: false));
} catch (e) {
emit(state.copyWith(isLoading: false));
emit(state.copyWith(financesModels: [], isLoading: false));
log(e.toString());
}
}
Future<void> setConsultationsPeriod(PeriodFilterStatus period) async {
Future<void> setFinancesPeriod(PeriodFilterStatus period) async {
emit(state.copyWith(periodFilterStatus: period));
await getConsultations();
await getConsultantFinances();
}
}

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -6,7 +7,7 @@ part 'consultant_finances_state.freezed.dart';
@freezed
abstract class ConsultantFinancesState with _$ConsultantFinancesState {
factory ConsultantFinancesState({
@Default([]) List<String> financesModels,
@Default([]) List<ConsultantFinanceModel> financesModels,
@Default(false) isLoading,
@Default(null) String? error,
@Default(null) PeriodFilterStatus? periodFilterStatus,

View File

@@ -0,0 +1,36 @@
import 'dart:developer';
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserPurchasesCubit extends Cubit<UserPurchasesState> {
final ConsultationsRepository consultationsRepository;
UserPurchasesCubit({
required this.consultationsRepository,
}) : super(
UserPurchasesState(),
);
Future<void> getUserPurchases() async {
try {
emit(state.copyWith(isLoading: true));
final purchasesModels = await consultationsRepository.getUserPurchases(
state.periodFilterStatus?.value,
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
);
emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false));
} catch (e) {
emit(state.copyWith(purchasesModels: [], isLoading: false));
log(e.toString());
}
}
Future<void> setPurchasesPeriod(PeriodFilterStatus period) async {
emit(state.copyWith(periodFilterStatus: period));
await getUserPurchases();
}
}

View File

@@ -0,0 +1,15 @@
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_purchases_state.freezed.dart';
@freezed
abstract class UserPurchasesState with _$UserPurchasesState {
factory UserPurchasesState({
@Default([]) List<UserPurchasesModel> purchasesModels,
@Default(false) isLoading,
@Default(null) String? error,
@Default(null) PeriodFilterStatus? periodFilterStatus,
}) = _UserPurchasesState;
}

View File

@@ -0,0 +1,33 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'consultant_finance_model.freezed.dart';
part 'consultant_finance_model.g.dart';
@freezed
abstract class ConsultantFinanceModel with _$ConsultantFinanceModel {
factory ConsultantFinanceModel({
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
@JsonKey(name: 'bank_id') required String bankId,
@JsonKey(name: 'title') required String title,
@JsonKey(name: 'clientName') required String clientName,
@JsonKey(name: 'duration') required int duration,
@JsonKey(name: 'payment_price', fromJson: _parsePayment) required String paymentPrice,
required PaymentStatus? paymentStatus,
}) = _ConsultantFinanceModel;
factory ConsultantFinanceModel.fromJson(Map<String, dynamic> json) => _$ConsultantFinanceModelFromJson(json);
}
String _parsePayment(json) {
return ((json as int) / 100).toStringAsFixed(2);
}
enum PaymentStatus {
@JsonValue('COMPLETED')
completed,
@JsonValue('REJECTED')
rejected,
@JsonValue('CHECKED')
checked
}

View File

@@ -0,0 +1,21 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_purchases_model.freezed.dart';
part 'user_purchases_model.g.dart';
@freezed
abstract class UserPurchasesModel with _$UserPurchasesModel {
factory UserPurchasesModel({
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
@JsonKey(name: 'bank_id') required String bankId,
@JsonKey(name: 'title') required String title,
@JsonKey(name: 'consultantName') required String consultantName,
@JsonKey(name: 'duration') required int duration,
@JsonKey(name: 'price') required int paymentPrice,
@JsonKey(name: 'chosen_plan_last_state') required ChosenPlanStateEnum chosenPlanState,
}) = _UserPurchasesModel;
factory UserPurchasesModel.fromJson(Map<String, dynamic> json) => _$UserPurchasesModelFromJson(json);
}

View File

@@ -1,281 +1,225 @@
// import 'package:eatvio_app/core/locator_service.dart';
// import 'package:eatvio_app/core/theme/custom_text_style.dart';
// import 'package:eatvio_app/core/theme/theme_helper.dart';
// import 'package:eatvio_app/core/utils/date_time_utils.dart';
// import 'package:eatvio_app/core/utils/localization_utils.dart';
// import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
// import 'package:eatvio_app/core/widgets/loader.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
// import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
// import 'package:eatvio_app/generated/l10n.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
// class ConsultantFinancesScreen extends StatefulWidget {
// const ConsultantFinancesScreen({super.key});
class ConsultantFinancesScreen extends StatefulWidget {
const ConsultantFinancesScreen({super.key});
static const name = 'consultantFinancesScreen';
static const path = '/consultant-finances-screen';
@override
State<ConsultantFinancesScreen> createState() => _ConsultantFinancesScreenState();
}
// @override
// State<ConsultantFinancesScreen> createState() => _ConsultantFinancesScreenState();
// }
// class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
// @override
// Widget build(BuildContext context) {
// final s = S.of(context);
// return BlocProvider<ConsultantFinancesCubit>(
// create: (context) => sl<ConsultantFinancesCubit>(),
// child: BlocBuilder<ConsultantFinancesCubit, ConsultantFinancesState>(
// builder: (context, state) {
// return Scaffold(
// backgroundColor: const Color(0xFFF0F2F5),
// appBar: AppBar(
// shape: const RoundedRectangleBorder(
// borderRadius: BorderRadiusDirectional.vertical(
// bottom: Radius.circular(12),
// ),
// ),
// title: const Text('Архив консультаций'),
// centerTitle: true,
// ),
// body: state.isLoading
// ? const Center(
// child: Loader(),
// )
// : SingleChildScrollView(
// padding: const EdgeInsets.symmetric(vertical: 8),
// child: Column(
// children: [
// Material(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// const SizedBox(height: 16),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16),
// child: Text(
// 'Всего консультаций: ${state.statisticModel.total}',
// style: const TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.w500,
// color: Color(0xFF6D6F71),
// height: 1,
// ),
// ),
// ),
// const SizedBox(height: 12),
// Row(
// children: [
// const SizedBox(width: 16),
// Material(
// color: const Color(0xFF0BB450).withAlpha(11),
// borderRadius: BorderRadius.circular(8),
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// child: Text(
// 'Завершено: ${state.statisticModel.finished}',
// style: const TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w400,
// color: Color(0xFF0BB450),
// height: 1,
// ),
// ),
// ),
// ),
// const SizedBox(width: 8),
// Material(
// color: const Color(0xFFD01717).withAlpha(11),
// borderRadius: BorderRadius.circular(8),
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// child: Text(
// 'Отменено: ${state.statisticModel.canceled}',
// style: const TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w400,
// color: Color(0xFFD01717),
// height: 1,
// ),
// ),
// ),
// ),
// ],
// ),
// const SizedBox(height: 16),
// ],
// ),
// ),
// const SizedBox(height: 8),
// Material(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// child: Padding(
// padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
// child: SingleChildScrollView(
// physics: const NeverScrollableScrollPhysics(),
// child: Column(
// children: [
// CustomDropDownButton<PeriodFilterStatus>(
// items: PeriodFilterStatus.values
// .map(
// (value) => DropdownMenuItem(
// value: value,
// child: Text(
// value.name,
// style: TextStyles.textMMedium.copyWith(color: appTheme.black),
// ),
// ),
// )
// .toList(),
// onChanged: (value) {
// if (value == null) return;
// context.read<ConsultationsArchiveCubit>().setConsultationsPeriod(value);
// },
// value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
// ),
// const SizedBox(height: 12),
// CustomDropDownButton<ConsultationFilterStatus>(
// items: ConsultationFilterStatus.values
// .map(
// (value) => DropdownMenuItem(
// value: value,
// child: Text(
// value.name,
// style: TextStyles.textMMedium.copyWith(color: appTheme.black),
// ),
// ),
// )
// .toList(),
// onChanged: (value) {
// if (value == null) return;
// context.read<ConsultationsArchiveCubit>().setConsultationsStatus(value);
// },
// value: state.consultationFilterStatus,
// ),
// const SizedBox(height: 12),
// if (state.consultations.isNotEmpty)
// ListView.separated(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// itemBuilder: (context, index) {
// return Material(
// color: const Color(0xFFF6F6F6),
// borderRadius: BorderRadius.circular(20),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// state.consultations[index].id.toString(),
// style: const TextStyle(
// color: Color(0xFF323F4B),
// height: 1,
// fontWeight: FontWeight.w500,
// ),
// ),
// Text(
// state.consultations[index].startDate.format('yyyy.MM.dd'),
// style: const TextStyle(
// color: Color(0xFF6D6F71),
// height: 1,
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// ),
// const SizedBox(height: 8),
// Text(
// state.consultations[index].planTitle,
// style: TextStyle(
// color: appTheme.blueA400,
// height: 1,
// fontSize: 18,
// fontWeight: FontWeight.w600,
// ),
// ),
// const SizedBox(height: 8),
// Text(
// 'Покупатель: ${state.consultations[index].userName}',
// style: const TextStyle(
// color: Color(0xFF323F4B),
// height: 1,
// fontSize: 14,
// fontWeight: FontWeight.w500,
// ),
// ),
// const SizedBox(height: 8),
// Text(
// '${state.consultations[index].duration} ${getDayForm(state.consultations[index].duration, s)}',
// style: const TextStyle(
// color: Color(0xFF323F4B),
// height: 1,
// fontSize: 14,
// fontWeight: FontWeight.w500,
// ),
// ),
// const SizedBox(height: 8),
// Container(
// height: 1,
// width: double.infinity,
// decoration: const BoxDecoration(
// color: Color(0xFFDEDEDF),
// ),
// ),
// const SizedBox(height: 8),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// '${state.consultations[index].consultantPayment.toStringAsFixed(2)} ₽',
// style: const TextStyle(
// color: Color(0xFF323F4B),
// height: 1,
// fontSize: 18,
// fontWeight: FontWeight.w600,
// ),
// ),
// StatusWidget(
// status: state.consultations[index].chosenPlanState,
// ),
// ],
// ),
// ],
// ),
// ),
// );
// },
// separatorBuilder: (context, index) => const SizedBox(height: 12),
// itemCount: state.financesModels.length,
// )
// else
// const Padding(
// padding: EdgeInsets.symmetric(vertical: 40.0),
// child: Text(
// 'У вас нет таких консультаций',
// style: TextStyles.textMMedium,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// );
// },
// ),
// );
// }
// }
class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider<ConsultantFinancesCubit>(
create: (context) => sl<ConsultantFinancesCubit>()..getConsultantFinances(),
child: BlocBuilder<ConsultantFinancesCubit, ConsultantFinancesState>(
builder: (context, state) {
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.vertical(
bottom: Radius.circular(12),
),
),
title: Text(
'Финансы',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
centerTitle: true,
),
body: state.isLoading
? const Center(
child: Loader(),
)
: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
InkWell(
onTap: () {
GoRouter.of(context).pushNamed(CardSettingsScreen.name);
},
borderRadius: BorderRadius.circular(20),
child: Ink(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
width: MediaQuery.of(context).size.width - 32,
child: const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Управление картами', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)),
),
),
),
),
const SizedBox(height: 8),
Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
CustomDropDownButton<PeriodFilterStatus>(
items: PeriodFilterStatus.values
.map(
(value) => DropdownMenuItem(
value: value,
child: Text(
value.name,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
)
.toList(),
onChanged: (value) {
if (value == null) return;
context.read<ConsultantFinancesCubit>().setFinancesPeriod(value);
},
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
),
const SizedBox(height: 12),
if (state.financesModels.isNotEmpty)
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final Status modelStatus = switch (state.financesModels[index].paymentStatus) {
null => Status.inProgress,
PaymentStatus.completed => Status.success,
PaymentStatus.rejected => Status.canceled,
PaymentStatus.checked => Status.success,
};
return Material(
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Builder(builder: (context) {
final idLen = state.financesModels[index].bankId.length;
return Text(
'${state.financesModels[index].bankId.substring(0, 4)}...${state.financesModels[index].bankId.substring(idLen - 4, idLen)} ',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
);
}),
Text(
state.financesModels[index].startDate.format('yyyy.MM.dd'),
style: const TextStyle(
color: Color(0xFF6D6F71),
height: 1,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 8),
Text(
state.financesModels[index].title,
style: TextStyle(
color: appTheme.blueA400,
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
'Покупатель: ${state.financesModels[index].clientName}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'${state.financesModels[index].duration} ${getDayForm(state.financesModels[index].duration, s)}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Container(
height: 1,
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFFDEDEDF),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${state.financesModels[index].paymentPrice} ₽',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
StatusWidget(
status: modelStatus,
),
],
),
],
),
),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemCount: state.financesModels.length,
)
else
const Padding(
padding: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
'У вас нет таких консультаций',
style: TextStyles.textMMedium,
),
),
],
),
),
),
),
],
)),
);
},
),
);
}
}

View File

@@ -9,13 +9,15 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/arc
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultationsArchiveScreen extends StatefulWidget {
const ConsultationsArchiveScreen({super.key});
static const name = 'consultationsArchiveScreen';
static const path = '/consultations-archive-screen';
@override
State<ConsultationsArchiveScreen> createState() => _ConsultationsArchiveScreenState();
}
@@ -164,6 +166,15 @@ class _ConsultationsArchiveScreenState extends State<ConsultationsArchiveScreen>
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final Status consultationStatus;
if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
consultationStatus = Status.canceled;
} else if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) {
consultationStatus = Status.success;
} else {
consultationStatus = Status.inProgress;
}
return Material(
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
@@ -245,7 +256,7 @@ class _ConsultationsArchiveScreenState extends State<ConsultationsArchiveScreen>
),
),
StatusWidget(
status: state.consultations[index].chosenPlanState,
status: consultationStatus,
),
],
),

View File

@@ -0,0 +1,209 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserPurchasesScreen extends StatefulWidget {
const UserPurchasesScreen({super.key});
static const name = 'userPurchasesScreen';
static const path = '/user-purchases-screen';
@override
State<UserPurchasesScreen> createState() => _UserPurchasesScreen();
}
class _UserPurchasesScreen extends State<UserPurchasesScreen> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider<UserPurchasesCubit>(
create: (context) => sl<UserPurchasesCubit>()..getUserPurchases(),
child: BlocBuilder<UserPurchasesCubit, UserPurchasesState>(
builder: (context, state) {
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.vertical(
bottom: Radius.circular(12),
),
),
title: Text(
'Финансы',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
centerTitle: true,
),
body: state.isLoading
? const Center(
child: Loader(),
)
: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
const SizedBox(height: 8),
Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
CustomDropDownButton<PeriodFilterStatus>(
items: PeriodFilterStatus.values
.map(
(value) => DropdownMenuItem(
value: value,
child: Text(
value.name,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
)
.toList(),
onChanged: (value) {
if (value == null) return;
context.read<UserPurchasesCubit>().setPurchasesPeriod(value);
},
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
),
const SizedBox(height: 12),
if (state.purchasesModels.isNotEmpty)
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final Status purchaseStatus;
if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
purchaseStatus = Status.canceled;
} else if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) {
purchaseStatus = Status.success;
} else {
purchaseStatus = Status.inProgress;
}
return Material(
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Builder(builder: (context) {
final idLen = state.purchasesModels[index].bankId.length;
return Text(
'${state.purchasesModels[index].bankId.substring(0, 4)}...${state.purchasesModels[index].bankId.substring(idLen - 4, idLen)} ',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
);
}),
Text(
state.purchasesModels[index].startDate.format('yyyy.MM.dd'),
style: const TextStyle(
color: Color(0xFF6D6F71),
height: 1,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 8),
Text(
state.purchasesModels[index].title,
style: TextStyle(
color: appTheme.blueA400,
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
'Продавец: ${state.purchasesModels[index].consultantName}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Container(
height: 1,
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFFDEDEDF),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${state.purchasesModels[index].paymentPrice} ₽',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
StatusWidget(
status: purchaseStatus,
),
],
),
],
),
),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemCount: state.purchasesModels.length,
)
else
const Padding(
padding: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
'У вас нет таких консультаций',
style: TextStyles.textMMedium,
),
),
],
),
),
),
),
],
)),
);
},
),
);
}
}

View File

@@ -1,15 +1,15 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:flutter/material.dart';
class StatusWidget extends StatelessWidget {
const StatusWidget({super.key, required this.status});
final ChosenPlanStateEnum status;
final Status status;
@override
Widget build(BuildContext context) {
if (status == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
return Material(
return switch (status){
Status.canceled => Material(
color: appTheme.lightRed,
borderRadius: BorderRadius.circular(8),
child: Padding(
@@ -24,9 +24,9 @@ class StatusWidget extends StatelessWidget {
),
),
),
);
} else if (status == ChosenPlanStateEnum.stateChargeWasCollected) {
return Material(
),
Status.success => Material(
color: const Color(0xFF0BB450).withAlpha(11),
borderRadius: BorderRadius.circular(8),
child: const Padding(
@@ -41,9 +41,9 @@ class StatusWidget extends StatelessWidget {
),
),
),
);
}
return Material(
),
Status.inProgress => Material(
color: appTheme.transparentBlue,
borderRadius: BorderRadius.circular(8),
child: Padding(
@@ -58,6 +58,13 @@ class StatusWidget extends StatelessWidget {
),
),
),
);
),
};
}
}
enum Status {
canceled,
success,
inProgress
}

View File

@@ -4,6 +4,9 @@ import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
@@ -40,9 +43,22 @@ class MenuScreen extends StatelessWidget {
leading: 'assets/icons/finance.svg',
title: 'Мои финансы',
onTap: () {
GoRouter.of(context).pushNamed('userFinanceScreen');
if (profileModel.roles.contains('consultant')) {
GoRouter.of(context).push(ConsultantFinancesScreen.path);
return;
}
GoRouter.of(context).push(UserPurchasesScreen.path);
},
),
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/finance.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);
},
),
_divider(context),
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
_divider(context),

View File

@@ -2,8 +2,10 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
@@ -26,6 +28,7 @@ import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_po
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -34,6 +37,7 @@ import 'package:go_router/go_router.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -440,12 +444,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
const Text(
'Дипломы\nи сертификаты',
style: TextStyle(
color: Color(0xFF323F4B),
@@ -454,19 +458,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
fontWeight: FontWeight.w600,
),
),
// GestureDetector(
// onTap: () {
// GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
// },
// child: Text(
// widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
// style: const TextStyle(
// color: Color(0xFF2F80ED),
// fontSize: 16,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
GestureDetector(
onTap: () {
GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
},
child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
style: const TextStyle(
color: Color(0xFF2F80ED),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
@@ -541,12 +545,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
const Text(
'Образование',
style: TextStyle(
color: Color(0xFF323F4B),
@@ -554,19 +558,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
fontWeight: FontWeight.w600,
),
),
// GestureDetector(
// onTap: () {
// GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
// },
// child: Text(
// widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
// style: const TextStyle(
// color: Color(0xFF2F80ED),
// fontSize: 16,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
GestureDetector(
onTap: () {
GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
},
child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
style: const TextStyle(
color: Color(0xFF2F80ED),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
@@ -600,9 +604,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
_educationBlock(String title, String date) {
final parsedDate = DateTime.tryParse(date);
final formattedDate = parsedDate != null
? DateFormat('d MMMM y', 'ru_RU').format(parsedDate)
: date;
final formattedDate = parsedDate != null ? DateFormat('d MMMM y', 'ru_RU').format(parsedDate) : date;
return SizedBox(
width: double.infinity,
@@ -640,7 +642,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_socialLinks(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
@@ -993,51 +994,70 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (value == 'replace') {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
builder: (context) {
final s = S.of(context);
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.camera,
title: s.allowAccessToCamera,
description: s.allowAccessToCameraDescription,
onGranted: () async {
AppRouter.navigatorKey.currentContext?.pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
),
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
CupertinoActionSheetAction(
onPressed: () async {
PermissionHandler.checkPermission(
permission: Permission.photos,
title: s.allowAcessToGalery,
description: s.allowAcessToGaleryDescription,
onGranted: () async {
AppRouter.navigatorKey.currentContext?.pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
},
);
}
if (value == 'delete') {

View File

@@ -1,12 +1,13 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.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_state.dart';
import 'package:flutter/material.dart';
@@ -93,26 +94,33 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
],
),
const Spacer(),
if (!state.blogPosts[widget.index].author.isEntitySubscribedByMe! && !state.blogPosts[widget.index].author.authorIsMe)
SubscribeButtonWidget(
slug: state.blogPosts[widget.index].author.authorSlug,
),
CustomPopup(
isAuthor: state.blogPosts[widget.index].author.authorIsMe,
complainedContentType: ComplainedContent.posts,
path: 'blog_post/${(state.blogPosts[widget.index].id)}',
id: state.blogPosts[widget.index].id,
feedBlogPostModel: state.blogPosts[widget.index],
),
],
),
),
if (state.blogPosts[widget.index].title.isNotEmpty || state.blogPosts[widget.index].description.isNotEmpty) const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
state.blogPosts[widget.index].title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
color: Colors.black87,
if (state.blogPosts[widget.index].title.isNotEmpty) ...[
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
state.blogPosts[widget.index].title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
color: Colors.black87,
),
),
),
),
if (state.blogPosts[widget.index].title.isNotEmpty && state.blogPosts[widget.index].description.isNotEmpty) const SizedBox(height: 8),
],
if (state.blogPosts[widget.index].title.isEmpty) const SizedBox(height: 8),
if (state.blogPosts[widget.index].title.isNotEmpty && state.blogPosts[widget.index].description.isNotEmpty)
const SizedBox(height: 8),
if (state.blogPosts[widget.index].description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
@@ -50,17 +51,25 @@ class RegistrationCubit extends Cubit<RegistrationState> {
updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
Future<void> tryRegistration() async {
try {
final token = await registrationRepository.tryRegister(
state.sheet.copyWith(
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
responseBody: '{}',
),
if (await registrationRepository.emailIsReachable(state.sheet.email)) {
try {
final token = await registrationRepository.tryRegister(
state.sheet.copyWith(
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
responseBody: '{}',
),
);
await TokenStorage.saveTokenInStorage(token);
AppRouter.navigatorKey.currentContext?.pushNamed('verifyEmailScreen');
} catch (e) {
log(e.toString());
}
} else {
showErrorDialog(
AppRouter.navigatorKey.currentContext!,
title: 'Ошибка регистрации',
description: 'Несуществующая почта.',
);
await TokenStorage.saveTokenInStorage(token);
AppRouter.navigatorKey.currentContext?.pushNamed('verifyEmailScreen');
} catch (e) {
log(e.toString());
}
}

View File

@@ -1,3 +1,5 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
import 'package:eatvio_app/features/registration/models/location_model.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart';
@@ -30,4 +32,17 @@ class RegistrationRepository {
rethrow;
}
}
Future<bool> emailIsReachable(String email) async {
try {
final response = await sl<Dio>().getUri(Uri.parse('https://api.validate.email/validate?email=$email'));
if (response.data['result']['reachable'] != 'invalid') {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
}

View File

@@ -73,6 +73,19 @@ class MessageLookup extends MessageLookupByLibrary {
"allergies_presence": MessageLookupByLibrary.simpleMessage(
"Наличие аллергии",
),
"allowAccess": MessageLookupByLibrary.simpleMessage("Разрешить доступ"),
"allowAccessToCamera": MessageLookupByLibrary.simpleMessage(
"Разрешите доступ к камере",
),
"allowAccessToCameraDescription": MessageLookupByLibrary.simpleMessage(
"Предоставление доступа позволит вам делать фотографии и добавлять к записям",
),
"allowAcessToGalery": MessageLookupByLibrary.simpleMessage(
"Разрешите доступ к фото",
),
"allowAcessToGaleryDescription": MessageLookupByLibrary.simpleMessage(
"Предоставление доступа позволит вам добавлять фото к записям",
),
"another_form_of_comment": MessageLookupByLibrary.simpleMessage(
"комментария",
),

View File

@@ -3344,6 +3344,56 @@ class S {
args: [],
);
}
/// `Разрешите доступ к фото`
String get allowAcessToGalery {
return Intl.message(
'Разрешите доступ к фото',
name: 'allowAcessToGalery',
desc: '',
args: [],
);
}
/// `Предоставление доступа позволит вам добавлять фото к записям`
String get allowAcessToGaleryDescription {
return Intl.message(
'Предоставление доступа позволит вам добавлять фото к записям',
name: 'allowAcessToGaleryDescription',
desc: '',
args: [],
);
}
/// `Разрешите доступ к камере`
String get allowAccessToCamera {
return Intl.message(
'Разрешите доступ к камере',
name: 'allowAccessToCamera',
desc: '',
args: [],
);
}
/// `Предоставление доступа позволит вам делать фотографии и добавлять к записям`
String get allowAccessToCameraDescription {
return Intl.message(
'Предоставление доступа позволит вам делать фотографии и добавлять к записям',
name: 'allowAccessToCameraDescription',
desc: '',
args: [],
);
}
/// `Разрешить доступ`
String get allowAccess {
return Intl.message(
'Разрешить доступ',
name: 'allowAccess',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -397,6 +397,11 @@
"planStarted": "План «{planName}» начался",
"userCanceldThePlanSubscription": "Пользователь {name} отменил подписку на план «{planName}»",
"userSubscribedToPlan": "Пользователь {name} подписался на план «{planName}»",
"markAsRead": "Отметить всё прочитанным"
"markAsRead": "Отметить всё прочитанным",
"allowAcessToGalery": "Разрешите доступ к фото",
"allowAcessToGaleryDescription": "Предоставление доступа позволит вам добавлять фото к записям",
"allowAccessToCamera": "Разрешите доступ к камере",
"allowAccessToCameraDescription": "Предоставление доступа позволит вам делать фотографии и добавлять к записям",
"allowAccess": "Разрешить доступ"
}

View File

@@ -21,7 +21,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await di.init();
await SentryFlutter.init((options) {

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.1.2+32
version: 1.1.4+34
publish_to: 'none'
environment:
@@ -69,6 +69,8 @@ dependencies:
shared_preferences: ^2.5.3
version: ^3.0.2
package_info_plus: ^8.3.0
device_info_plus: ^11.4.0
permission_handler: ^12.0.0+1
dev_dependencies:
build_runner: ^2.4.15