Compare commits

...

14 Commits

Author SHA1 Message Date
Username1548
45774c3875 fix 2025-08-14 16:58:00 +03:00
Username1548
62c0914fe2 fix 2025-08-14 16:55:15 +03:00
Username1548
48f5b64223 feat(emailVerification): added timer for email code resending 2025-08-14 16:54:13 +03:00
Username1548
91bb5cf483 feat(menu): fixed indentation 2025-08-12 09:01:31 +03:00
Username1548
c4dedac3f3 added build date 2025-08-12 12:27:58 +07:00
mso7nueh
8ed243f63c Merge pull request 'fix: upd ver' (#638) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#638
2025-08-11 14:19:46 +00:00
mso7nueh
a10fe6d2f5 fix: upd ver 2025-08-11 17:19:14 +03:00
mso7nueh
35b78bdeee Merge pull request 'fix(blackList+notifications): Issue-523, 191' (#636) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#636
2025-08-11 14:00:25 +00:00
mso7nueh
ef037eb4d5 Merge pull request 'feat(chats): added message resend and deleting, feat(menu): added build number' (#633) from misha/eatvio_app:issue-594 into main
Reviewed-on: eatvio/eatvio_app#633
2025-08-11 14:00:14 +00:00
mso7nueh
5a3385cacf Merge pull request 'removed public offer from ui on ios' (#637) from misha/eatvio_app:issue-635 into main
Reviewed-on: eatvio/eatvio_app#637
2025-08-11 14:00:05 +00:00
Username1548
ef4d91a5d8 removed public offert from ui on ios 2025-08-11 20:46:09 +07:00
mso7nueh
dca4b4c471 fix(blackList+notifications): Issue-523, 191 2025-08-11 14:53:43 +03:00
mso7nueh
cfbd7597fa Merge pull request 'fix(archive_of_consultatons): Issue-331' (#631) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#631
2025-08-08 10:43:27 +00:00
mso7nueh
412ac0aeeb fix(archive_of_consultatons): Issue-331 2025-08-08 13:43:04 +03:00
24 changed files with 814 additions and 386 deletions

View File

@@ -144,7 +144,11 @@ jobs:
run: flutter test run: flutter test
- name: Build apk - name: Build apk
run: flutter build apk --release --dart-define=HIDE_PLANS=false run: |
BUILD_DATE=$(TZ=Europe/Moscow date +'%Y-%m-%d %H:%M:%S')
flutter build apk --release \
--dart-define=HIDE_PLANS=false \
--dart-define=BUILD_DATE="$BUILD_DATE"
- name: Rename and move apk - name: Rename and move apk
run: | run: |

View File

@@ -396,7 +396,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 67; CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = VL68C36HYS; DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@@ -546,7 +546,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 67; CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = VL68C36HYS; DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@@ -584,7 +584,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 67; CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = VL68C36HYS; DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>67</string> <string>68</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@@ -46,13 +46,15 @@ platform :ios do
match(type: "appstore", readonly: true) match(type: "appstore", readonly: true)
increment_build_number(xcodeproj: "Runner.xcodeproj") increment_build_number(xcodeproj: "Runner.xcodeproj")
build_date = Time.now.getlocal("+03:00").strftime("%Y-%m-%d %H:%M:%S")
build_app( build_app(
workspace: "Runner.xcworkspace", workspace: "Runner.xcworkspace",
scheme: "Runner", scheme: "Runner",
export_method: "app-store", export_method: "app-store",
codesigning_identity: "Apple Distribution", codesigning_identity: "Apple Distribution",
xcargs: "--dart-define=HIDE_PLANS=true" xcargs: "--dart-define=HIDE_PLANS=true --dart-define=BUILD_DATE='#{build_date}'"
) )

View File

@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/env_config.dart';
String baseUrl = EnvConfig.baseUrl; String baseUrl = EnvConfig.baseUrl;
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false); bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
const buildDate = String.fromEnvironment('BUILD_DATE', defaultValue: '');
const List<int> durationValues = [7, 14, 21]; const List<int> durationValues = [7, 14, 21];

View File

@@ -73,10 +73,7 @@ class _AuthScreenState extends State<AuthScreen> {
rethrow; rethrow;
} }
} }
AppRouter.navigatorKey.currentContext?.go( AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
'/feed-screen',
extra: {'needToInitializeWebSocket': false},
);
} }
@override @override
@@ -102,11 +99,7 @@ class _AuthScreenState extends State<AuthScreen> {
GoRouter.of(context).pushNamed(DevToolsScreen.name); GoRouter.of(context).pushNamed(DevToolsScreen.name);
} }
}, },
child: SvgPicture.asset( child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: isFocused ? 64 : MediaQuery.of(context).size.width - 64, fit: BoxFit.contain),
ImageConstant.imgLogoEatvio,
height: isFocused ? 64 : MediaQuery.of(context).size.width - 64,
fit: BoxFit.contain,
),
), ),
), ),
body: SafeArea( body: SafeArea(
@@ -137,10 +130,7 @@ class _AuthScreenState extends State<AuthScreen> {
hidePassword = !hidePassword; hidePassword = !hidePassword;
}); });
}, },
icon: Icon( icon: Icon(hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, color: Colors.grey),
hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
color: Colors.grey,
),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -149,11 +139,7 @@ class _AuthScreenState extends State<AuthScreen> {
onTap: () => GoRouter.of(context).goNamed('forgetPasswordScreen', extra: false), onTap: () => GoRouter.of(context).goNamed('forgetPasswordScreen', extra: false),
child: Text( child: Text(
'Забыли пароль?', 'Забыли пароль?',
style: TextStyle( style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
color: appTheme.blueA400,
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
), ),
), ),
), ),
@@ -166,11 +152,7 @@ class _AuthScreenState extends State<AuthScreen> {
onTap: () => GoRouter.of(context).goNamed('registrationScreen'), onTap: () => GoRouter.of(context).goNamed('registrationScreen'),
child: Text( child: Text(
'Зарегистрироваться', 'Зарегистрироваться',
style: TextStyle( style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
color: appTheme.blueA400,
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
), ),
), ),
], ],
@@ -181,23 +163,22 @@ class _AuthScreenState extends State<AuthScreen> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
text: TextSpan( text: TextSpan(
text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n', text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n',
style: const TextStyle( style: const TextStyle(fontSize: 12, color: Colors.black),
fontSize: 12,
color: Colors.black,
),
children: [ children: [
const TextSpan(text: 'c '), const TextSpan(text: 'c '),
TextSpan( if (!hidePlans) ...[
text: ' Публичной офертой', TextSpan(
style: TextStyle( text: ' Публичной офертой',
fontSize: 12, style: TextStyle(
color: appTheme.blueA400, fontSize: 12,
decorationColor: appTheme.blueA400, color: appTheme.blueA400,
decoration: TextDecoration.underline, decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
), ),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')), const TextSpan(text: ' и '),
), ],
const TextSpan(text: ' и '),
TextSpan( TextSpan(
text: 'Политикой конфиденциальности\nи защитой персональных данных', text: 'Политикой конфиденциальности\nи защитой персональных данных',
style: TextStyle( style: TextStyle(
@@ -223,17 +204,12 @@ class _AuthScreenState extends State<AuthScreen> {
highlightElevation: 0, highlightElevation: 0,
elevation: 0, elevation: 0,
focusElevation: 0, focusElevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
borderRadius: BorderRadius.circular(20),
),
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400, backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
onPressed: state is AuthStateLoading onPressed: state is AuthStateLoading
? null ? null
: () async { : () async {
final isAuthenticated = await authCubit.getTokenByCredentials( final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text);
emailController.text,
passwordController.text,
);
if (isAuthenticated) { if (isAuthenticated) {
if (context.mounted) { if (context.mounted) {
_reconnectWebSocket(context); _reconnectWebSocket(context);
@@ -242,10 +218,7 @@ class _AuthScreenState extends State<AuthScreen> {
}, },
label: state is AuthStateLoading label: state is AuthStateLoading
? const CupertinoActivityIndicator() ? const CupertinoActivityIndicator()
: Text( : Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
'Войти',
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -257,9 +230,7 @@ class _AuthScreenState extends State<AuthScreen> {
highlightElevation: 0, highlightElevation: 0,
elevation: 0, elevation: 0,
focusElevation: 0, focusElevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
borderRadius: BorderRadius.circular(16),
),
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400, backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
onPressed: state is AuthStateLoading onPressed: state is AuthStateLoading
? null ? null
@@ -274,14 +245,10 @@ class _AuthScreenState extends State<AuthScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( const Text('Где взять QR-код?\n', style: TextStyle(fontSize: 18)),
'Где взять QR-код?\n',
style: TextStyle(
fontSize: 18,
),
),
Text( Text(
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"'), '1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@@ -293,15 +260,10 @@ class _AuthScreenState extends State<AuthScreen> {
}, },
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
child: const Text( child: const Text(
'Отмена', 'Отмена',
style: TextStyle( style: TextStyle(color: Colors.black, fontWeight: FontWeight.w400),
color: Colors.black,
fontWeight: FontWeight.w400,
),
), ),
), ),
), ),
@@ -314,15 +276,10 @@ class _AuthScreenState extends State<AuthScreen> {
}, },
child: Ink( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
child: Text( child: Text(
'Перейти', 'Перейти',
style: TextStyle( style: TextStyle(color: appTheme.blueA400, fontWeight: FontWeight.w600),
color: appTheme.blueA400,
fontWeight: FontWeight.w600,
),
), ),
), ),
), ),
@@ -337,10 +294,7 @@ class _AuthScreenState extends State<AuthScreen> {
}, },
label: state is AuthStateLoading label: state is AuthStateLoading
? const CupertinoActivityIndicator() ? const CupertinoActivityIndicator()
: const Icon( : const Icon(Icons.qr_code_scanner_rounded, color: Colors.white),
Icons.qr_code_scanner_rounded,
color: Colors.white,
),
), ),
), ),
], ],

View File

@@ -13,6 +13,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
@@ -446,7 +448,27 @@ class _ChatScreenState extends State<ChatScreen> {
const SizedBox(width: 8), const SizedBox(width: 8),
InkWell( InkWell(
customBorder: const CircleBorder(), customBorder: const CircleBorder(),
onTap: () { onTap: () async {
// Проверяем, заблокирован ли пользователь
final blockedUsersCubit = sl<BlockedUsersCubit>();
await blockedUsersCubit.getBlockedUsers();
final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == state.chatRoom.users.receiver.slug);
if (isBlocked) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
backgroundColor: Colors.red,
),
);
}
return;
}
}
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) { if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
chatRoomCubit.sendMessage( chatRoomCubit.sendMessage(
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(), data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),

View File

@@ -12,6 +12,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
class EmptyChatScreen extends StatefulWidget { class EmptyChatScreen extends StatefulWidget {
final Author author; final Author author;
@@ -133,21 +135,45 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
InkWell( InkWell(
customBorder: const CircleBorder(), customBorder: const CircleBorder(),
onTap: () async { onTap: () async {
// Проверяем, заблокирован ли пользователь
final blockedUsersCubit = sl<BlockedUsersCubit>();
await blockedUsersCubit.getBlockedUsers();
final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug);
if (isBlocked) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
backgroundColor: Colors.red,
),
);
}
return;
}
}
if (textEditingController.text.isNotEmpty) { if (textEditingController.text.isNotEmpty) {
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(
data: textEditingController.text.trim(), data: textEditingController.text.trim(),
slug: widget.author.authorSlug, slug: widget.author.authorSlug,
); );
setState(() { if (mounted) {
textEditingController.clear(); setState(() {
}); textEditingController.clear();
});
}
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug); final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( if (context.mounted) {
'chatScreen', AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
extra: { 'chatScreen',
'uuid': chatIdOrError, extra: {
}, 'uuid': chatIdOrError,
); },
);
}
} }
}, },
child: Ink( child: Ink(
@@ -175,20 +201,44 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
CupertinoActionSheetAction( CupertinoActionSheetAction(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
// Проверяем, заблокирован ли пользователь
final blockedUsersCubit = sl<BlockedUsersCubit>();
await blockedUsersCubit.getBlockedUsers();
final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug);
if (isBlocked) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
backgroundColor: Colors.red,
),
);
}
return;
}
}
final picker = ImagePicker(); final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera); List<XFile>? images = await picker.pickMultiImage();
if (image != null) { if (images.isNotEmpty) {
List<File> files = images.map((image) => File(image.path)).toList();
await sl<ChatRepository>().sendMessage( await sl<ChatRepository>().sendMessage(
files: [File(image.path)], files: files,
slug: widget.author.authorSlug, slug: widget.author.authorSlug,
); );
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug); final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( if (mounted) {
'chatScreen', AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
extra: { 'chatScreen',
'uuid': chatIdOrError, extra: {
}, 'uuid': chatIdOrError,
); },
);
}
} }
}, },
child: const Row( child: const Row(
@@ -211,12 +261,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
slug: widget.author.authorSlug, slug: widget.author.authorSlug,
); );
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug); final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( if (mounted) {
'chatScreen', AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
extra: { 'chatScreen',
'uuid': chatIdOrError, extra: {
}, 'uuid': chatIdOrError,
); },
);
}
} }
}, },
child: const Row( child: const Row(
@@ -261,12 +313,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
slug: widget.author.authorSlug, slug: widget.author.authorSlug,
); );
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug); final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
AppRouter.navigatorKey.currentContext?.pushReplacementNamed( if (mounted) {
'chatScreen', AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
extra: { 'chatScreen',
'uuid': chatIdOrError, extra: {
}, 'uuid': chatIdOrError,
); },
);
}
} }
}, },
child: const Row( child: const Row(

View File

@@ -25,24 +25,16 @@ class _LastStepScreenState extends State<LastStepScreen> {
children: [ children: [
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Text( child: Text('Последний шаг', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1)),
'Последний шаг',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
height: 1.1,
),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
CheckboxListTile( CheckboxListTile(
value: checkboxState, value: checkboxState,
title: const Text( title: Text(
'Я ознакомлен и принимаю договор публичной офёрты', hidePlans
style: TextStyle( ? 'Я ознакомлен и принимаю политику конфиденциальности и защиту персональных данных'
fontSize: 18, : 'Я ознакомлен и принимаю договор публичной офёрты',
height: 1, style: const TextStyle(fontSize: 18, height: 1),
),
), ),
onChanged: (_) { onChanged: (_) {
setState(() { setState(() {
@@ -53,25 +45,14 @@ class _LastStepScreenState extends State<LastStepScreen> {
activeColor: appTheme.blueA400, activeColor: appTheme.blueA400,
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(horizontal: 16),
), ),
Padding( Padding(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: _divider(context)),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: _divider(context),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),
child: TextButton( child: TextButton(
onPressed: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')), onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '$baseUrl/docs/privacy.pdf' : '$baseUrl/oferta_bs.pdf')),
child: const Text( child: Text(
'Договор офёрты', hidePlans ? 'Политика конфиденциальности и пользовательское соглашение' : 'Договор офёрты',
style: TextStyle( style: const TextStyle(height: 1, fontSize: 16, color: Color(0xFF323F4B), decoration: TextDecoration.underline),
height: 1,
fontSize: 16,
color: Color(0xFF323F4B),
decoration: TextDecoration.underline,
),
), ),
), ),
), ),
@@ -84,10 +65,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
return Container( return Container(
height: 0.4, height: 0.4,
width: MediaQuery.of(context).size.width - 32, width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration( decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
color: Colors.black26,
borderRadius: BorderRadius.circular(16),
),
); );
} }
} }

View File

@@ -50,6 +50,7 @@ class ConsultationService {
else if (state != null && stateComparison != null) else if (state != null && stateComparison != null)
'filters[last_state][${stateComparison.value}]': state, 'filters[last_state][${stateComparison.value}]': state,
if (dateComparison != null && date != null) 'filters[start_date][${dateComparison.value}]': date, if (dateComparison != null && date != null) 'filters[start_date][${dateComparison.value}]': date,
'sort[]': 'created_at:desc',
}, },
); );
return ConsultationPaginationModel.fromJson(response.data); return ConsultationPaginationModel.fromJson(response.data);

View File

@@ -31,9 +31,7 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
dateComparison: state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, dateComparison: state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
stateComparison: state.consultationFilterStatus == ConsultationFilterStatus.all stateComparison: state.consultationFilterStatus == ConsultationFilterStatus.all
? null ? null
: state.consultationFilterStatus == ConsultationFilterStatus.inProgress : FiltersComparisonEnum.equal,
? FiltersComparisonEnum.notInArray
: FiltersComparisonEnum.equal,
); );
emit(state.copyWith(consultations: consultations, isLoading: false)); emit(state.copyWith(consultations: consultations, isLoading: false));
} catch (e) { } catch (e) {
@@ -67,20 +65,17 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
enum ConsultationFilterStatus { enum ConsultationFilterStatus {
all, all,
canceled, canceled,
finished, finished;
inProgress;
String get name => switch (this) { String get name => switch (this) {
ConsultationFilterStatus.all => 'Все консультации', ConsultationFilterStatus.all => 'Все консультации',
ConsultationFilterStatus.canceled => 'Отменён', ConsultationFilterStatus.canceled => 'Отменён',
ConsultationFilterStatus.finished => 'Завершён', ConsultationFilterStatus.finished => 'Завершён',
ConsultationFilterStatus.inProgress => 'В процессе',
}; };
String? get value => switch (this) { String? get value => switch (this) {
ConsultationFilterStatus.all => null, ConsultationFilterStatus.all => null,
ConsultationFilterStatus.canceled => 'canceled_by_the_initiator', ConsultationFilterStatus.canceled => 'canceled_by_the_initiator',
ConsultationFilterStatus.finished => 'charge_was_collected', ConsultationFilterStatus.finished => 'charge_was_collected',
ConsultationFilterStatus.inProgress => 'active',
}; };
} }

View File

@@ -195,7 +195,7 @@ class _ConsultationsArchiveScreenState extends State<ConsultationsArchiveScreen>
), ),
), ),
Text( Text(
state.consultations[index].startDate.format('yyyy.MM.dd'), state.consultations[index].startDate.format('dd.MM.yy'),
style: const TextStyle( style: const TextStyle(
color: Color(0xFF6D6F71), color: Color(0xFF6D6F71),
height: 1, height: 1,

View File

@@ -156,7 +156,13 @@ class _AppInfoState extends State<AppInfo> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsetsGeometry.only(left: 16), padding: const EdgeInsetsGeometry.only(left: 16),
child: Text('Версия приложения: $version ($buildNumber)', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Версия приложения: $version ($buildNumber)', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
if (buildDate != '') Text('Дата сборки: $buildDate', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
],
),
), ),
); );
} }

View File

@@ -538,6 +538,297 @@ class EntityCommented implements BaseNotificationModel {
} }
} }
class PlanSubscriptionExpiredNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String planName;
final String customerName;
@override
final DateTime createdAt;
PlanSubscriptionExpiredNotification({
required this.type,
required this.planName,
required this.customerName,
required this.isRead,
required this.createdAt,
});
factory PlanSubscriptionExpiredNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionExpiredNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class PlanSubscriptionRenewedNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String planName;
final String customerName;
final String renewalDate;
@override
final DateTime createdAt;
PlanSubscriptionRenewedNotification({
required this.type,
required this.planName,
required this.customerName,
required this.renewalDate,
required this.isRead,
required this.createdAt,
});
factory PlanSubscriptionRenewedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionRenewedNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
renewalDate: json['renewal_date'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class ConsultantAssignedNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String consultantName;
final String planName;
final int planId;
@override
final DateTime createdAt;
ConsultantAssignedNotification({
required this.type,
required this.consultantName,
required this.planName,
required this.planId,
required this.isRead,
required this.createdAt,
});
factory ConsultantAssignedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantAssignedNotification(
type: type,
consultantName: json['consultant_name'],
planName: json['plan_name'],
planId: json['plan_id'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class ConsultantUnassignedNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String consultantName;
final String planName;
final int planId;
@override
final DateTime createdAt;
ConsultantUnassignedNotification({
required this.type,
required this.consultantName,
required this.planName,
required this.planId,
required this.isRead,
required this.createdAt,
});
factory ConsultantUnassignedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantUnassignedNotification(
type: type,
consultantName: json['consultant_name'],
planName: json['plan_name'],
planId: json['plan_id'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class PaymentFailedNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String planName;
final String errorMessage;
final String paymentMethod;
@override
final DateTime createdAt;
PaymentFailedNotification({
required this.type,
required this.planName,
required this.errorMessage,
required this.paymentMethod,
required this.isRead,
required this.createdAt,
});
factory PaymentFailedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return PaymentFailedNotification(
type: type,
planName: json['plan_name'],
errorMessage: json['error_message'],
paymentMethod: json['payment_method'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class PaymentSuccessfulNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String planName;
final double amount;
final String currency;
final String transactionId;
@override
final DateTime createdAt;
PaymentSuccessfulNotification({
required this.type,
required this.planName,
required this.amount,
required this.currency,
required this.transactionId,
required this.isRead,
required this.createdAt,
});
factory PaymentSuccessfulNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return PaymentSuccessfulNotification(
type: type,
planName: json['plan_name'],
amount: (json['amount'] as num).toDouble(),
currency: json['currency'],
transactionId: json['transaction_id'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class GoalAchievedNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String goalName;
final String goalType;
final double achievedValue;
final double targetValue;
@override
final DateTime createdAt;
GoalAchievedNotification({
required this.type,
required this.goalName,
required this.goalType,
required this.achievedValue,
required this.targetValue,
required this.isRead,
required this.createdAt,
});
factory GoalAchievedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return GoalAchievedNotification(
type: type,
goalName: json['goal_name'],
goalType: json['goal_type'],
achievedValue: (json['achieved_value'] as num).toDouble(),
targetValue: (json['target_value'] as num).toDouble(),
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class WorkoutReminderNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String workoutName;
final String workoutType;
final int duration;
@override
final DateTime createdAt;
WorkoutReminderNotification({
required this.type,
required this.workoutName,
required this.workoutType,
required this.duration,
required this.isRead,
required this.createdAt,
});
factory WorkoutReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return WorkoutReminderNotification(
type: type,
workoutName: json['workout_name'],
workoutType: json['workout_type'],
duration: json['duration'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class MealReminderNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
final String mealName;
final String mealType;
final int calories;
@override
final DateTime createdAt;
MealReminderNotification({
required this.type,
required this.mealName,
required this.mealType,
required this.calories,
required this.isRead,
required this.createdAt,
});
factory MealReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return MealReminderNotification(
type: type,
mealName: json['meal_name'],
mealType: json['meal_type'],
calories: json['calories'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
class UnknownNotification implements BaseNotificationModel { class UnknownNotification implements BaseNotificationModel {
@override @override
final NotificationType type; final NotificationType type;
@@ -570,6 +861,16 @@ enum NotificationType {
commentReplied, commentReplied,
scoreAdded, scoreAdded,
entityCommented, entityCommented,
// Дополнительные типы уведомлений
planSubscriptionExpired,
planSubscriptionRenewed,
consultantAssigned,
consultantUnassigned,
paymentFailed,
paymentSuccessful,
goalAchieved,
workoutReminder,
mealReminder,
unknown; unknown;
static NotificationType getTypeFromString(String type) => switch (type) { static NotificationType getTypeFromString(String type) => switch (type) {
@@ -593,6 +894,16 @@ enum NotificationType {
'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied, 'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied,
'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded, 'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded,
'App\\Notifications\\Comment\\EntityCommented' => NotificationType.entityCommented, 'App\\Notifications\\Comment\\EntityCommented' => NotificationType.entityCommented,
// Дополнительные маппинги
'App\\Notifications\\Plan\\PlanSubscriptionExpired' => NotificationType.planSubscriptionExpired,
'App\\Notifications\\Plan\\PlanSubscriptionRenewed' => NotificationType.planSubscriptionRenewed,
'App\\Notifications\\Plan\\ConsultantAssigned' => NotificationType.consultantAssigned,
'App\\Notifications\\Plan\\ConsultantUnassigned' => NotificationType.consultantUnassigned,
'App\\Notifications\\Payment\\PaymentFailed' => NotificationType.paymentFailed,
'App\\Notifications\\Payment\\PaymentSuccessful' => NotificationType.paymentSuccessful,
'App\\Notifications\\Goal\\GoalAchieved' => NotificationType.goalAchieved,
'App\\Notifications\\Workout\\WorkoutReminder' => NotificationType.workoutReminder,
'App\\Notifications\\Meal\\MealReminder' => NotificationType.mealReminder,
_ => NotificationType.unknown, _ => NotificationType.unknown,
}; };
} }

View File

@@ -113,6 +113,16 @@ class NotificationsCubit extends Cubit<NotificationsState> {
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
// Новые типы уведомлений
NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.paymentFailed => PaymentFailedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.goalAchieved => GoalAchievedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.mealReminder => MealReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.unknown => UnknownNotification(type: type), NotificationType.unknown => UnknownNotification(type: type),
}; };
} catch (e) { } catch (e) {

View File

@@ -47,6 +47,16 @@ class NotificationWidget extends StatelessWidget {
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied), CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded), ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented), EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
// Новые типы уведомлений
PlanSubscriptionExpiredNotification() => _PlanSubscriptionExpiredWidget(notification: notification as PlanSubscriptionExpiredNotification),
PlanSubscriptionRenewedNotification() => _PlanSubscriptionRenewedWidget(notification: notification as PlanSubscriptionRenewedNotification),
ConsultantAssignedNotification() => _ConsultantAssignedWidget(notification: notification as ConsultantAssignedNotification),
ConsultantUnassignedNotification() => _ConsultantUnassignedWidget(notification: notification as ConsultantUnassignedNotification),
PaymentFailedNotification() => _PaymentFailedWidget(notification: notification as PaymentFailedNotification),
PaymentSuccessfulNotification() => _PaymentSuccessfulWidget(notification: notification as PaymentSuccessfulNotification),
GoalAchievedNotification() => _GoalAchievedWidget(notification: notification as GoalAchievedNotification),
WorkoutReminderNotification() => _WorkoutReminderWidget(notification: notification as WorkoutReminderNotification),
MealReminderNotification() => _MealReminderWidget(notification: notification as MealReminderNotification),
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification), UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
}; };
} }
@@ -443,3 +453,121 @@ class _UnknownNotificationWidget extends StatelessWidget {
); );
} }
} }
// Новые виджеты для дополнительных типов уведомлений
class _PlanSubscriptionExpiredWidget extends StatelessWidget {
const _PlanSubscriptionExpiredWidget({required this.notification});
final PlanSubscriptionExpiredNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Подписка на план "${notification.planName}" истекла',
createdAt: notification.createdAt,
);
}
}
class _PlanSubscriptionRenewedWidget extends StatelessWidget {
const _PlanSubscriptionRenewedWidget({required this.notification});
final PlanSubscriptionRenewedNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Подписка на план "${notification.planName}" продлена до ${notification.renewalDate}',
createdAt: notification.createdAt,
);
}
}
class _ConsultantAssignedWidget extends StatelessWidget {
const _ConsultantAssignedWidget({required this.notification});
final ConsultantAssignedNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'К плану "${notification.planName}" назначен консультант ${notification.consultantName}',
createdAt: notification.createdAt,
);
}
}
class _ConsultantUnassignedWidget extends StatelessWidget {
const _ConsultantUnassignedWidget({required this.notification});
final ConsultantUnassignedNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Консультант ${notification.consultantName} отстранен от плана "${notification.planName}"',
createdAt: notification.createdAt,
);
}
}
class _PaymentFailedWidget extends StatelessWidget {
const _PaymentFailedWidget({required this.notification});
final PaymentFailedNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Ошибка оплаты плана "${notification.planName}": ${notification.errorMessage}',
createdAt: notification.createdAt,
);
}
}
class _PaymentSuccessfulWidget extends StatelessWidget {
const _PaymentSuccessfulWidget({required this.notification});
final PaymentSuccessfulNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Успешная оплата плана "${notification.planName}" на сумму ${notification.amount} ${notification.currency}',
createdAt: notification.createdAt,
);
}
}
class _GoalAchievedWidget extends StatelessWidget {
const _GoalAchievedWidget({required this.notification});
final GoalAchievedNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Достигнута цель "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue}',
createdAt: notification.createdAt,
);
}
}
class _WorkoutReminderWidget extends StatelessWidget {
const _WorkoutReminderWidget({required this.notification});
final WorkoutReminderNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Напоминание о тренировке "${notification.workoutName}" (${notification.duration} мин)',
createdAt: notification.createdAt,
);
}
}
class _MealReminderWidget extends StatelessWidget {
const _MealReminderWidget({required this.notification});
final MealReminderNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Напоминание о приеме пищи "${notification.mealName}" (${notification.calories} ккал)',
createdAt: notification.createdAt,
);
}
}

View File

@@ -119,6 +119,61 @@ class NotificationsService {
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
// Новые типы уведомлений
NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.paymentFailed => PaymentFailedNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.goalAchieved => GoalAchievedNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.mealReminder => MealReminderNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
}; };
} catch (e) { } catch (e) {

View File

@@ -19,6 +19,7 @@ abstract class ProfileModel with _$ProfileModel {
@JsonKey(name: "updated_at") DateTime? updatedAt, @JsonKey(name: "updated_at") DateTime? updatedAt,
@JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile, @JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile,
@JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe, @JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe,
@JsonKey(name: "is_blocked_by_user", fromJson: parseBool, defaultValue: false) required bool isBlockedByUser,
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe, @JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
@JsonKey(name: "slug") required String slug, @JsonKey(name: "slug") required String slug,
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl, @JsonKey(name: "profile_photo_url") required String profilePhotoUrl,

View File

@@ -93,6 +93,39 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
isFirstFetch = false; isFirstFetch = false;
} }
// Проверяем, заблокировал ли пользователь меня
if (state.profile.isBlockedByUser) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.block,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'Пользователь ограничил доступ к своей странице',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
],
),
),
),
);
}
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state); return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state);
} else if (state is ProfileStateError) { } else if (state is ProfileStateError) {
return Scaffold( return Scaffold(
@@ -174,10 +207,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
_subscriptionsAndSubscribers(state), _subscriptionsAndSubscribers(state),
if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)), if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _socialLinks(state), if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _socialLinks(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _education(state), if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _education(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _diplomasAndCertificates(state), if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _diplomasAndCertificates(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans) _activePlans(state), if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans && !state.profile.isBlockedByMe) _activePlans(state),
_blogPosts(state), _blogPosts(state),
], ],
), ),

View File

@@ -1,5 +1,10 @@
import 'dart:async';
import 'package:eatvio_app/core/locator_service.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/toasts.dart'; import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -18,11 +23,14 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
final List<TextEditingController> _controllers = List.generate(4, (_) => TextEditingController()); final List<TextEditingController> _controllers = List.generate(4, (_) => TextEditingController());
String email = ''; String email = '';
late final GoRouter goRouter; late final GoRouter goRouter;
late Timer _timer;
int _secondsRemaining = 60;
bool _canSendAgain = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
goRouter = GoRouter.of(context); goRouter = GoRouter.of(context);
_startTimer();
} }
@override @override
@@ -36,6 +44,23 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
super.dispose(); super.dispose();
} }
void _startTimer() {
setState(() {
_canSendAgain = false;
_secondsRemaining = 60;
});
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_secondsRemaining > 0) {
_secondsRemaining--;
} else {
_canSendAgain = true;
_timer.cancel();
}
});
});
}
void _checkAndSubmitCode(BuildContext context) { void _checkAndSubmitCode(BuildContext context) {
final code = _controllers.map((c) => c.text).join(); final code = _controllers.map((c) => c.text).join();
if (code.length == 4) { if (code.length == 4) {
@@ -74,18 +99,25 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
children: [ children: [
Text("Код отправлен на почту\n$email", textAlign: TextAlign.center), Text("Код отправлен на почту\n$email", textAlign: TextAlign.center),
const SizedBox(height: 20), const SizedBox(height: 20),
Row( Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, (index) => _buildCodeField(context, index))),
mainAxisAlignment: MainAxisAlignment.spaceEvenly, const SizedBox(height: 40),
children: List.generate(4, (index) => _buildCodeField(context, index)), CustomOutlinedButton(
), text: 'Запросить код повторно',
const SizedBox(height: 20), height: 52,
const Text("Запросить код повторно", style: TextStyle(color: Colors.grey)), isDisabled: !_canSendAgain,
TextButton( buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
onPressed: () { buttonStyle: ButtonStyle(
context.read<VerifyEmailCubit>().sendEmailCode(); side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(_canSendAgain ? appTheme.blueA400 : appTheme.gray),
),
onPressed: () async {
await context.read<VerifyEmailCubit>().sendEmailCode();
_startTimer();
}, },
child: const Text("Не приходит код?"),
), ),
const SizedBox(height: 8),
if (!_canSendAgain)
Text("Зпросить код повторно можно через $_secondsRemaining с.", style: TextStyles.textSMedium.copyWith(color: appTheme.gray)),
], ],
), ),
), ),

View File

@@ -51,9 +51,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (BuildContext context, SettingsState state) { builder: (BuildContext context, SettingsState state) {
if (state is SettingsStateLoaded) { if (state is SettingsStateLoaded) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Настройки')),
title: const Text('Настройки'),
),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
@@ -64,8 +62,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
const SizedBox(height: 12), const SizedBox(height: 12),
_notifications(state), _notifications(state),
const SizedBox(height: 12), const SizedBox(height: 12),
_aboutEatvio(state), if (!hidePlans) ...[_aboutEatvio(state), const SizedBox(height: 12)],
const SizedBox(height: 12),
_becomeConsultantButton(state), _becomeConsultantButton(state),
_deleteAccountButton(state, settingsCubit), _deleteAccountButton(state, settingsCubit),
], ],
@@ -76,16 +73,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
if (state is SettingsStateError) { if (state is SettingsStateError) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Настройки')),
title: const Text('Настройки'),
),
body: DataErrorWidget(onReload: () => settingsCubit.init()), body: DataErrorWidget(onReload: () => settingsCubit.init()),
); );
} }
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Настройки')),
title: const Text('Настройки'),
),
body: const Loader(), body: const Loader(),
); );
}, },
@@ -101,22 +94,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Text( child: Text(
'Дополнительаная информация', 'Дополнительаная информация',
style: TextStyle( style: TextStyle(fontSize: 16, height: 1, color: Colors.grey, fontFamily: 'Mulish'),
fontSize: 16,
height: 1,
color: Colors.grey,
fontFamily: 'Mulish',
),
), ),
), ),
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'generalInfoScreen', 'generalInfoScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Общая информация', title: 'Общая информация',
), ),
@@ -124,11 +108,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'physicalFormScreen', 'physicalFormScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Физическая форма', title: 'Физическая форма',
), ),
@@ -136,11 +116,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'pressureScreen', 'pressureScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Давление', title: 'Давление',
), ),
@@ -148,11 +124,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'otherInfoScreen', 'otherInfoScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Прочая информация', title: 'Прочая информация',
), ),
@@ -160,31 +132,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'physicalActivityScreen', 'physicalActivityScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Физическая нагрузка', title: 'Физическая нагрузка',
), ),
_divider(context), _divider(context),
_tile( _tile(onTap: () => GoRouter.of(context).pushNamed('forgetPasswordScreen', extra: true), title: 'Сменить пароль'),
onTap: () => GoRouter.of(context).pushNamed(
'forgetPasswordScreen',
extra: true,
),
title: 'Сменить пароль',
),
_divider(context), _divider(context),
_tile( _tile(
onTap: () => GoRouter.of(context).pushNamed( onTap: () => GoRouter.of(context).pushNamed(
'changeEmailScreen', 'changeEmailScreen',
extra: SettingsEntity( extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
profileSettingsModel: state.profileSettings,
profileDetailsModel: state.profileDetails,
settingsCubit: settingsCubit,
),
), ),
title: 'Сменить Email', title: 'Сменить Email',
), ),
@@ -198,14 +156,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [ children: [
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Text( child: Text('Уведомления', style: TextStyle(fontSize: 16, height: 1, color: Colors.grey)),
'Уведомления',
style: TextStyle(
fontSize: 16,
height: 1,
color: Colors.grey,
),
),
), ),
_tile( _tile(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -213,11 +164,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() { setState(() {
updatesNotificationsSwitchState = !updatesNotificationsSwitchState; updatesNotificationsSwitchState = !updatesNotificationsSwitchState;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(newUpdatesNotifications: updatesNotificationsSwitchState));
profileSettingsModel: state.profileSettings.copyWith(
newUpdatesNotifications: updatesNotificationsSwitchState,
),
);
}, },
title: 'Уведомление о новых обновлениях', title: 'Уведомление о новых обновлениях',
trailing: CupertinoSwitch( trailing: CupertinoSwitch(
@@ -228,9 +175,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
updatesNotificationsSwitchState = newValue; updatesNotificationsSwitchState = newValue;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(
profileSettingsModel: state.profileSettings.copyWith( profileSettingsModel: state.profileSettings.copyWith(newUpdatesNotifications: updatesNotificationsSwitchState),
newUpdatesNotifications: updatesNotificationsSwitchState,
),
); );
}, },
), ),
@@ -243,9 +188,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
fillDiaryNotificationsSwitchState = !fillDiaryNotificationsSwitchState; fillDiaryNotificationsSwitchState = !fillDiaryNotificationsSwitchState;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(
profileSettingsModel: state.profileSettings.copyWith( profileSettingsModel: state.profileSettings.copyWith(diaryReminderNotifications: fillDiaryNotificationsSwitchState),
diaryReminderNotifications: fillDiaryNotificationsSwitchState,
),
); );
}, },
title: 'Напоминание о заполнении дневника', title: 'Напоминание о заполнении дневника',
@@ -257,9 +200,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
fillDiaryNotificationsSwitchState = newValue; fillDiaryNotificationsSwitchState = newValue;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(
profileSettingsModel: state.profileSettings.copyWith( profileSettingsModel: state.profileSettings.copyWith(diaryReminderNotifications: fillDiaryNotificationsSwitchState),
diaryReminderNotifications: fillDiaryNotificationsSwitchState,
),
); );
}, },
), ),
@@ -271,11 +212,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() { setState(() {
moodSwitchState = !moodSwitchState; moodSwitchState = !moodSwitchState;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isMoodFillable: moodSwitchState));
profileSettingsModel: state.profileSettings.copyWith(
isMoodFillable: moodSwitchState,
),
);
}, },
title: 'Указывать “настроение” в заполнение дневника', title: 'Указывать “настроение” в заполнение дневника',
trailing: CupertinoSwitch( trailing: CupertinoSwitch(
@@ -285,11 +222,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() { setState(() {
moodSwitchState = newValue; moodSwitchState = newValue;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isMoodFillable: moodSwitchState));
profileSettingsModel: state.profileSettings.copyWith(
isMoodFillable: moodSwitchState,
),
);
}, },
), ),
), ),
@@ -300,11 +233,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() { setState(() {
bedTimeSwitchState = !bedTimeSwitchState; bedTimeSwitchState = !bedTimeSwitchState;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isBedtimeFillable: bedTimeSwitchState));
profileSettingsModel: state.profileSettings.copyWith(
isBedtimeFillable: bedTimeSwitchState,
),
);
}, },
title: 'Указывать “отход ко сну” в заполнение дневника', title: 'Указывать “отход ко сну” в заполнение дневника',
trailing: CupertinoSwitch( trailing: CupertinoSwitch(
@@ -314,11 +243,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() { setState(() {
bedTimeSwitchState = newValue; bedTimeSwitchState = newValue;
}); });
settingsCubit.updateProfileSettings( settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isBedtimeFillable: bedTimeSwitchState));
profileSettingsModel: state.profileSettings.copyWith(
isBedtimeFillable: bedTimeSwitchState,
),
);
}, },
), ),
), ),
@@ -332,24 +257,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [ children: [
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Text( child: Text('О Eatvio', style: TextStyle(fontSize: 16, height: 1, color: Colors.grey)),
'О Eatvio',
style: TextStyle(
fontSize: 16,
height: 1,
color: Colors.grey,
),
),
),
_tile(
title: 'О компании',
onTap: () async => await launchUrl(Uri.parse('$baseUrl/ru/company-details')),
), ),
_tile(title: 'О компании', onTap: () async => await launchUrl(Uri.parse('$baseUrl/ru/company-details'))),
_divider(context), _divider(context),
_tile( _tile(title: 'Пользовательское соглашение', onTap: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf'))),
title: 'Пользовательское соглашение',
onTap: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
),
], ],
); );
} }
@@ -364,19 +276,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: Ink( child: Ink(
height: 52, height: 52,
width: MediaQuery.of(context).size.width - 16, width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: const Center( child: const Center(
child: Text( child: Text('Стать консультантом', style: TextStyle(color: Colors.white, height: 1, fontSize: 18)),
'Стать консультантом',
style: TextStyle(
color: Colors.white,
height: 1,
fontSize: 18,
),
),
), ),
), ),
), ),
@@ -388,19 +290,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: Ink( child: Ink(
height: 52, height: 52,
width: MediaQuery.of(context).size.width - 16, width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
borderRadius: BorderRadius.circular(20),
color: const Color(0xFFEAEAEA),
),
child: const Center( child: const Center(
child: Text( child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
'Данные на проверке',
style: TextStyle(
color: Colors.black,
height: 1,
fontSize: 18,
),
),
), ),
), ),
); );
@@ -413,19 +305,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: Ink( child: Ink(
height: 52, height: 52,
width: MediaQuery.of(context).size.width - 16, width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0x33FFCC00)),
borderRadius: BorderRadius.circular(20),
color: const Color(0x33FFCC00),
),
child: const Center( child: const Center(
child: Text( child: Text('Заявка обработана', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
'Заявка обработана',
style: TextStyle(
color: Colors.black,
height: 1,
fontSize: 18,
),
),
), ),
), ),
), ),
@@ -444,57 +326,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
userRootNavigator: true, userRootNavigator: true,
context: context, context: context,
height: null, height: null,
child: DeleteAccountBottomSheet( child: DeleteAccountBottomSheet(cubit: settingsCubit),
cubit: settingsCubit,
),
); );
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
height: 52, height: 52,
width: MediaQuery.of(context).size.width - 16, width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
borderRadius: BorderRadius.circular(20),
color: const Color(0xFFEAEAEA),
),
child: const Center( child: const Center(
child: Text( child: Text('Удалить аккаунт', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
'Удалить аккаунт',
style: TextStyle(
color: Colors.black,
height: 1,
fontSize: 18,
),
),
), ),
), ),
), ),
); );
} }
ListTile _tile({ ListTile _tile({required String title, required Function() onTap, Widget? trailing, EdgeInsets? padding}) {
required String title,
required Function() onTap,
Widget? trailing,
EdgeInsets? padding,
}) {
return ListTile( return ListTile(
contentPadding: padding, contentPadding: padding,
onTap: onTap, onTap: onTap,
title: Text( title: Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1)),
title, trailing: trailing ?? const Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Colors.black38),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
trailing: trailing ??
const Icon(
Icons.arrow_forward_ios_rounded,
size: 12,
color: Colors.black38,
),
); );
} }
@@ -503,10 +356,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: Container( child: Container(
height: 0.4, height: 0.4,
width: MediaQuery.of(context).size.width - 32, width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration( decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
color: Colors.black26,
borderRadius: BorderRadius.circular(16),
),
), ),
); );
} }
@@ -530,33 +380,23 @@ class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const SizedBox( const SizedBox(height: 8),
height: 8,
),
const BottomSheetHandlerBar(), const BottomSheetHandlerBar(),
const SizedBox( const SizedBox(height: 20),
height: 20,
),
Text(s.accountDeleting, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), Text(s.accountDeleting, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
const SizedBox( const SizedBox(height: 16),
height: 16,
),
Text( Text(
s.youShureYouWantToDeleteAccount, s.youShureYouWantToDeleteAccount,
style: TextStyles.textSMedium.copyWith(color: appTheme.black), style: TextStyles.textSMedium.copyWith(color: appTheme.black),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox( const SizedBox(height: 8),
height: 8,
),
Text( Text(
s.itCantBeRecovered, s.itCantBeRecovered,
style: TextStyles.textSMedium.copyWith(color: appTheme.black), style: TextStyles.textSMedium.copyWith(color: appTheme.black),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox( const SizedBox(height: 8),
height: 8,
),
CustomOutlinedButton( CustomOutlinedButton(
text: s.delete, text: s.delete,
height: 52, height: 52,
@@ -573,9 +413,7 @@ class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
showErrorDialog(context, description: s.cannotDeleteAccount); showErrorDialog(context, description: s.cannotDeleteAccount);
}, },
), ),
const SizedBox( const SizedBox(height: 40),
height: 40,
),
], ],
), ),
); );

View File

@@ -69,15 +69,17 @@ class SupportScreen extends StatelessWidget {
child: Text('Правовые документы', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), child: Text('Правовые документы', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
InkWell( if (!hidePlans) ...[
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/public_offer.pdf')), InkWell(
borderRadius: BorderRadius.circular(16), onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/public_offer.pdf')),
child: const Padding( borderRadius: BorderRadius.circular(16),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: const Padding(
child: Text('Публичная офёрта', style: TextStyle(fontSize: 16)), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text('Публичная офёрта', style: TextStyle(fontSize: 16)),
),
), ),
), const SizedBox(height: 8),
const SizedBox(height: 8), ],
InkWell( InkWell(
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')), onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),

View File

@@ -1,6 +1,6 @@
name: eatvio_app name: eatvio_app
description: A new Flutter project. description: A new Flutter project.
version: 1.2.8+68 version: 1.2.9+69
publish_to: 'none' publish_to: 'none'
environment: environment:

View File

@@ -17,6 +17,7 @@ void main() {
(_) async => await Future.value( (_) async => await Future.value(
const ProfileModel( const ProfileModel(
id: 0, id: 0,
isBlockedByUser: false,
name: 'Alexander Avanesyan', name: 'Alexander Avanesyan',
email: 'mso7nueh@mail.ru', email: 'mso7nueh@mail.ru',
phoneConfirmationState: '', phoneConfirmationState: '',