forked from eatvio/eatvio_app
Merge pull request 'build 69 😅' (#639) from main into test-flight
Reviewed-on: eatvio/eatvio_app#639
This commit is contained in:
3
assets/icons/resend.svg
Normal file
3
assets/icons/resend.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.377 11L15.3193 6.94225C15.1641 6.78709 15.0865 6.60892 15.0865 6.40775C15.0865 6.20642 15.1641 6.02817 15.3193 5.873C15.4744 5.72817 15.6542 5.65734 15.8587 5.6605C16.0631 5.66384 16.2345 5.73467 16.373 5.873L20.8673 10.3673C20.9609 10.4609 21.0269 10.5597 21.0653 10.6635C21.1038 10.7673 21.123 10.8795 21.123 11C21.123 11.1205 21.1038 11.2327 21.0653 11.3365C21.0269 11.4403 20.9609 11.5391 20.8673 11.6328L16.373 16.127C16.2178 16.282 16.0422 16.357 15.8462 16.352C15.6501 16.3468 15.4744 16.2718 15.3193 16.127C15.1641 15.9718 15.0856 15.7946 15.0837 15.5953C15.0817 15.3959 15.1551 15.2218 15.3038 15.073L19.377 11ZM14.0693 11.75H7.25C6.3525 11.75 5.5865 12.0673 4.952 12.702C4.31733 13.3365 4 14.1025 4 15V17.75C4 17.9628 3.92817 18.141 3.7845 18.2845C3.641 18.4282 3.46283 18.5 3.25 18.5C3.03717 18.5 2.859 18.4282 2.7155 18.2845C2.57183 18.141 2.5 17.9628 2.5 17.75V15C2.5 13.6872 2.9635 12.5673 3.8905 11.6405C4.81733 10.7135 5.93717 10.25 7.25 10.25H14.0693L10.7615 6.94225C10.6065 6.78709 10.529 6.60892 10.529 6.40775C10.529 6.20642 10.6065 6.02817 10.7615 5.873C10.9167 5.72817 11.0965 5.65734 11.301 5.6605C11.5055 5.66384 11.677 5.73467 11.8155 5.873L16.3095 10.3673C16.4032 10.4609 16.4692 10.5597 16.5078 10.6635C16.5463 10.7673 16.5655 10.8795 16.5655 11C16.5655 11.1205 16.5463 11.2327 16.5078 11.3365C16.4692 11.4403 16.4032 11.5391 16.3095 11.6328L11.8155 16.127C11.6603 16.282 11.4847 16.357 11.2885 16.352C11.0923 16.3468 10.9167 16.2718 10.7615 16.127C10.6065 15.9718 10.528 15.7946 10.526 15.5953C10.524 15.3959 10.5974 15.2218 10.7463 15.073L14.0693 11.75Z" fill="#2F80ED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -396,7 +396,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -546,7 +546,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -584,7 +584,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>67</string>
|
||||
<string>68</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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/bottom_sheet_action_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void showCommentsActionsBottomSheet(
|
||||
@@ -123,7 +122,7 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
if (widget.isMyComment)
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.edit,
|
||||
iconPath: 'assets/icons/edit_paint.svg',
|
||||
needToPop: false,
|
||||
@@ -132,13 +131,13 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
}),
|
||||
)
|
||||
else if (widget.reply != null)
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.reply,
|
||||
iconPath: 'assets/icons/reply.svg',
|
||||
onTap: widget.reply!,
|
||||
),
|
||||
if (widget.reply != null) const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.copy,
|
||||
iconPath: 'assets/icons/copy2.svg',
|
||||
onTap: widget.copy,
|
||||
@@ -146,21 +145,21 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
if (widget.isMyComment) ...[
|
||||
if (widget.reply != null) ...[
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.reply,
|
||||
iconPath: 'assets/icons/reply.svg',
|
||||
onTap: widget.reply!,
|
||||
),
|
||||
],
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.deleteComment,
|
||||
iconPath: 'assets/icons/red_trash.svg',
|
||||
onTap: widget.delete,
|
||||
),
|
||||
] else ...[
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.complain,
|
||||
iconPath: 'assets/icons/complain.svg',
|
||||
onTap: widget.complain,
|
||||
@@ -172,33 +171,3 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionWidget extends StatelessWidget {
|
||||
const _ActionWidget({required this.text, required this.iconPath, required this.onTap, this.needToPop = true});
|
||||
final String text;
|
||||
final String iconPath;
|
||||
final void Function() onTap;
|
||||
final bool needToPop;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (needToPop) Navigator.pop(context);
|
||||
onTap();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyles.textLMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(iconPath)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
class EnvConfig {
|
||||
static String get baseUrl => dotenv.env['BASE_URL'] ?? 'https://eatvio.ru';
|
||||
static String get devBaseUrl => dotenv.env['DEV_BASE_URL'] ?? 'https://dev2.eatvio.com';
|
||||
static String get baseUrl => dotenv.env['BASE_URL'] ?? '';
|
||||
static String get devBaseUrl => dotenv.env['DEV_BASE_URL'] ?? '';
|
||||
|
||||
// Sentry Configuration
|
||||
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2';
|
||||
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? '';
|
||||
|
||||
// Firebase Configuration
|
||||
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? 'eatvio-3467b';
|
||||
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app';
|
||||
static String get firebaseStorageBucket => dotenv.env['FIREBASE_STORAGE_BUCKET'] ?? 'eatvio-3467b.firebasestorage.app';
|
||||
static String get firebaseMessagingSenderId => dotenv.env['FIREBASE_MESSAGING_SENDER_ID'] ?? '613980912922';
|
||||
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? '';
|
||||
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? '';
|
||||
static String get firebaseStorageBucket => dotenv.env['FIREBASE_STORAGE_BUCKET'] ?? '';
|
||||
static String get firebaseMessagingSenderId => dotenv.env['FIREBASE_MESSAGING_SENDER_ID'] ?? '';
|
||||
|
||||
// Firebase Android Configuration
|
||||
static String get firebaseAndroidApiKey => dotenv.env['FIREBASE_ANDROID_API_KEY'] ?? 'AIzaSyBUe1UqAAMfgMcd4hWdRoLF1Os-yn-HE9k';
|
||||
static String get firebaseAndroidAppId => dotenv.env['FIREBASE_ANDROID_APP_ID'] ?? '1:613980912922:android:4de95e5f2d50ba69ecc0a3';
|
||||
static String get firebaseAndroidApiKey => dotenv.env['FIREBASE_ANDROID_API_KEY'] ?? '';
|
||||
static String get firebaseAndroidAppId => dotenv.env['FIREBASE_ANDROID_APP_ID'] ?? '';
|
||||
|
||||
// Firebase iOS Configuration
|
||||
static String get firebaseIosApiKey => dotenv.env['FIREBASE_IOS_API_KEY'] ?? 'AIzaSyBnE8tQjL9_1HuXrTnqNy6qbXpoenO36yQ';
|
||||
static String get firebaseIosAppId => dotenv.env['FIREBASE_IOS_APP_ID'] ?? '1:613980912922:ios:c557d140a1212e63ecc0a3';
|
||||
static String get firebaseIosBundleId => dotenv.env['FIREBASE_IOS_BUNDLE_ID'] ?? 'com.eatvioapp.app';
|
||||
static String get firebaseIosApiKey => dotenv.env['FIREBASE_IOS_API_KEY'] ?? '';
|
||||
static String get firebaseIosAppId => dotenv.env['FIREBASE_IOS_APP_ID'] ?? '';
|
||||
static String get firebaseIosBundleId => dotenv.env['FIREBASE_IOS_BUNDLE_ID'] ?? '';
|
||||
|
||||
// App Store URLs
|
||||
static String get appStoreUrl => dotenv.env['APP_STORE_URL'] ?? 'https://apps.apple.com/app/idYOUR_APP_ID';
|
||||
static String get rustoreUrl => dotenv.env['RUSTORE_URL'] ?? 'https://www.rustore.ru/catalog/app/com.eatvioapp.app';
|
||||
static String get appStoreUrl => dotenv.env['APP_STORE_URL'] ?? '';
|
||||
static String get rustoreUrl => dotenv.env['RUSTORE_URL'] ?? '';
|
||||
|
||||
// Telegram Support
|
||||
static String get telegramSupportUrl => dotenv.env['TELEGRAM_SUPPORT_URL'] ?? 'https://t.me/eatvio';
|
||||
static String get supportEmail => dotenv.env['SUPPORT_EMAIL'] ?? 'help@eatv.io';
|
||||
static String get telegramSupportUrl => dotenv.env['TELEGRAM_SUPPORT_URL'] ?? '';
|
||||
static String get supportEmail => dotenv.env['SUPPORT_EMAIL'] ?? '';
|
||||
|
||||
// Test Credentials (for development only)
|
||||
static String get testEmail => dotenv.env['TEST_EMAIL'] ?? 'reshetnick.mike+user1@gmail.com';
|
||||
static String get testPassword => dotenv.env['TEST_PASSWORD'] ?? '1234567890';
|
||||
static String get testEmail => dotenv.env['TEST_EMAIL'] ?? '';
|
||||
static String get testPassword => dotenv.env['TEST_PASSWORD'] ?? '';
|
||||
|
||||
// Email Validation API
|
||||
static String get emailValidationApiUrl => dotenv.env['EMAIL_VALIDATION_API_URL'] ?? 'https://api.validate.email/validate';
|
||||
static String get emailValidationApiUrl => dotenv.env['EMAIL_VALIDATION_API_URL'] ?? '';
|
||||
|
||||
// Connection Check URLs
|
||||
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? 'https://google.com';
|
||||
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? 'https://yandex.ru';
|
||||
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? '';
|
||||
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? '';
|
||||
}
|
||||
|
||||
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class BottomSheetActionWidget extends StatelessWidget {
|
||||
const BottomSheetActionWidget({super.key, required this.text, required this.iconPath, required this.onTap, this.needToPop = true, this.color});
|
||||
final String text;
|
||||
final String iconPath;
|
||||
final void Function() onTap;
|
||||
final bool needToPop;
|
||||
final Color? color;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (needToPop) Navigator.pop(context);
|
||||
onTap();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(text, style: TextStyles.textLMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
SvgPicture.asset(iconPath, colorFilter: color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,10 +73,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.go(
|
||||
'/feed-screen',
|
||||
extra: {'needToInitializeWebSocket': false},
|
||||
);
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -102,11 +99,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
ImageConstant.imgLogoEatvio,
|
||||
height: isFocused ? 64 : MediaQuery.of(context).size.width - 64,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: isFocused ? 64 : MediaQuery.of(context).size.width - 64, fit: BoxFit.contain),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
@@ -137,10 +130,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
hidePassword = !hidePassword;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
|
||||
color: Colors.grey,
|
||||
),
|
||||
icon: Icon(hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -149,11 +139,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
onTap: () => GoRouter.of(context).goNamed('forgetPasswordScreen', extra: false),
|
||||
child: Text(
|
||||
'Забыли пароль?',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -166,11 +152,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
onTap: () => GoRouter.of(context).goNamed('registrationScreen'),
|
||||
child: Text(
|
||||
'Зарегистрироваться',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -181,23 +163,22 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black,
|
||||
),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black),
|
||||
children: [
|
||||
const TextSpan(text: 'c '),
|
||||
TextSpan(
|
||||
text: ' Публичной офертой',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.blueA400,
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
if (!hidePlans) ...[
|
||||
TextSpan(
|
||||
text: ' Публичной офертой',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.blueA400,
|
||||
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(
|
||||
text: 'Политикой конфиденциальности\nи защитой персональных данных',
|
||||
style: TextStyle(
|
||||
@@ -223,17 +204,12 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
highlightElevation: 0,
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||
onPressed: state is AuthStateLoading
|
||||
? null
|
||||
: () async {
|
||||
final isAuthenticated = await authCubit.getTokenByCredentials(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
);
|
||||
final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text);
|
||||
if (isAuthenticated) {
|
||||
if (context.mounted) {
|
||||
_reconnectWebSocket(context);
|
||||
@@ -242,10 +218,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
},
|
||||
label: state is AuthStateLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
: Text(
|
||||
'Войти',
|
||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
||||
),
|
||||
: Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -257,9 +230,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
highlightElevation: 0,
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||
onPressed: state is AuthStateLoading
|
||||
? null
|
||||
@@ -274,14 +245,10 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Где взять QR-код?\n',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const Text('Где взять QR-код?\n', style: TextStyle(fontSize: 18)),
|
||||
Text(
|
||||
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"'),
|
||||
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
@@ -293,15 +260,10 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
},
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||
child: const Text(
|
||||
'Отмена',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -314,15 +276,10 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
},
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(
|
||||
'Перейти',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: TextStyle(color: appTheme.blueA400, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -337,10 +294,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
},
|
||||
label: state is AuthStateLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
: const Icon(
|
||||
Icons.qr_code_scanner_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
: const Icon(Icons.qr_code_scanner_rounded, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -15,7 +15,11 @@ abstract class ChatMessageModel with _$ChatMessageModel {
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') required String? roomId,
|
||||
@Default(MessageState.sent) @JsonKey(includeToJson: false, includeFromJson: false) MessageState messageState,
|
||||
@Default(null) @JsonKey(includeToJson: false, includeFromJson: false) String? uuid,
|
||||
}) = _ChatMessageModel;
|
||||
|
||||
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
|
||||
}
|
||||
|
||||
enum MessageState { loading, sent, error }
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
@@ -19,6 +20,7 @@ import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
final ChatRepository chatRepository;
|
||||
@@ -31,12 +33,9 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
Timer? _timer;
|
||||
var uuid = const Uuid();
|
||||
|
||||
ChatRoomCubit({
|
||||
required this.chatRepository,
|
||||
required this.wsManager,
|
||||
required this.cardsRepository,
|
||||
}) : super(const ChatRoomState.loading());
|
||||
ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading());
|
||||
|
||||
void setTimer() {
|
||||
_timer?.cancel();
|
||||
@@ -89,10 +88,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initChat(
|
||||
String uuid, {
|
||||
bool sendInitialMessage = false,
|
||||
}) async {
|
||||
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
||||
try {
|
||||
final chatRoom = await chatRepository.getChatRoom(uuid);
|
||||
ChosenPlanModel? chosenPlan;
|
||||
@@ -171,11 +167,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
List<ChatMessageModel> messagesList = [message, ...(state as ChatRoomStateLoaded).chatMessages];
|
||||
chatRepository.emitNewMessageToChats(message);
|
||||
emit((state as ChatRoomStateLoaded).copyWith(chatMessages: messagesList, isLoading: isLoading));
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
const Duration(milliseconds: 300),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
);
|
||||
EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms());
|
||||
});
|
||||
|
||||
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
||||
@@ -183,22 +175,14 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
EasyDebounce.debounce(
|
||||
'client-typing',
|
||||
const Duration(seconds: 3),
|
||||
() => emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
isTyping: false,
|
||||
isLoading: isLoading,
|
||||
),
|
||||
),
|
||||
() => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> sendMessage({
|
||||
String? data,
|
||||
List<File>? files,
|
||||
required String userSlug,
|
||||
}) async {
|
||||
Future<bool> sendMessage({String? data, List<File>? files, required String userSlug}) async {
|
||||
try {
|
||||
final messageUuid = uuid.v4();
|
||||
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
emit(
|
||||
@@ -213,16 +197,38 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '',
|
||||
messageState: MessageState.loading,
|
||||
uuid: messageUuid,
|
||||
),
|
||||
...(state as ChatRoomStateLoaded).chatMessages,
|
||||
],
|
||||
),
|
||||
);
|
||||
await chatRepository.sendMessage(
|
||||
data: data,
|
||||
files: compressedFiles,
|
||||
slug: userSlug,
|
||||
);
|
||||
|
||||
try {
|
||||
await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == messageUuid) {
|
||||
return elem.copyWith(messageState: MessageState.sent);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == messageUuid) {
|
||||
return elem.copyWith(messageState: MessageState.error);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
sl<ChatsCubit>().getChatRooms();
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
@@ -268,7 +274,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
try {
|
||||
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
if (planId == null) return false;
|
||||
await chatRepository.complainAboutConsultation(id: planId, body: PlanComplainModel(description: description));
|
||||
await chatRepository.complainAboutConsultation(
|
||||
id: planId,
|
||||
body: PlanComplainModel(description: description),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
@@ -296,4 +305,70 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
typingSubscription?.cancel();
|
||||
super.close();
|
||||
}
|
||||
|
||||
void deleteMessage(String uuid) {
|
||||
final currentState = state;
|
||||
if (currentState is ChatRoomStateLoaded) {
|
||||
emit(currentState.copyWith(chatMessages: currentState.chatMessages.where((message) => message.uuid != uuid).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resendMessage({required String uuid, required String userSlug}) async {
|
||||
final currentState = state;
|
||||
if (currentState is ChatRoomStateLoaded) {
|
||||
final message = currentState.chatMessages.firstWhereOrNull((message) => message.uuid == uuid);
|
||||
if (message != null) {
|
||||
try {
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: [
|
||||
ChatMessageModel(
|
||||
id: 1,
|
||||
data: message.data,
|
||||
mediaData: message.mediaData,
|
||||
authorId: 1,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '',
|
||||
messageState: MessageState.loading,
|
||||
uuid: uuid,
|
||||
),
|
||||
...(state as ChatRoomStateLoaded).chatMessages.where((message) => message.uuid != uuid),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await chatRepository.sendMessage(data: message.data, files: message.mediaData as List<File>?, slug: userSlug);
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == uuid) {
|
||||
return elem.copyWith(messageState: MessageState.sent);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == uuid) {
|
||||
return elem.copyWith(messageState: MessageState.error);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
sl<ChatsCubit>().getChatRooms();
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/chat_bubble_widget.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/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
|
||||
@@ -93,7 +95,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (state is ChatRoomStateLoaded) {
|
||||
return _buildChat(context, state);
|
||||
} else if (state is ChatRoomStateError) {
|
||||
return Scaffold(appBar: AppBar(), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)));
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
|
||||
);
|
||||
} else {
|
||||
return const Scaffold(body: Loader());
|
||||
}
|
||||
@@ -168,34 +173,19 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFEBF4FF),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
decoration: const BoxDecoration(color: Color(0xFFEBF4FF), shape: BoxShape.circle),
|
||||
child: Text(
|
||||
state.chatRoom.users.receiver.name.split(' ').first[0] + state.chatRoom.users.receiver.name.split(' ')[1][0],
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7F9CF5),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 20,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFF7F9CF5), fontWeight: FontWeight.w400, fontSize: 20),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.chatRoom.users.receiver.name,
|
||||
style: CustomTextStyles.titleSmallBold.copyWith(
|
||||
color: PrimaryColors().black,
|
||||
),
|
||||
),
|
||||
Text(state.chatRoom.users.receiver.name, style: CustomTextStyles.titleSmallBold.copyWith(color: PrimaryColors().black)),
|
||||
AnimatedCrossFade(
|
||||
firstChild: Text(
|
||||
'Печатает...',
|
||||
style: CustomTextStyles.bodySmallMulishPrimary,
|
||||
),
|
||||
firstChild: Text('Печатает...', style: CustomTextStyles.bodySmallMulishPrimary),
|
||||
firstCurve: Curves.easeIn,
|
||||
secondCurve: Curves.easeOut,
|
||||
secondChild: const SizedBox.shrink(),
|
||||
@@ -251,9 +241,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (!result) return;
|
||||
if (!context.mounted) return;
|
||||
context.read<ChatsCubit>().reduceUnreadMessagesCount(
|
||||
chatId: state.chatRoom.id,
|
||||
count: unreadMessagesIdsCopy.length,
|
||||
);
|
||||
chatId: state.chatRoom.id,
|
||||
count: unreadMessagesIdsCopy.length,
|
||||
);
|
||||
unreadMessagesIdsCopy.clear();
|
||||
});
|
||||
}
|
||||
@@ -264,6 +254,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
messageState: state.chatMessages[index].messageState,
|
||||
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -290,6 +283,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
messageState: state.chatMessages[index].messageState,
|
||||
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -361,11 +357,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final imageIndex = _imageIndexAt(pickedFiles, i);
|
||||
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: imageFiles.map((e) => e.path).toList(),
|
||||
isLocal: true,
|
||||
index: imageIndex,
|
||||
),
|
||||
PhotoViewScreen(imageUrls: imageFiles.map((e) => e.path).toList(), isLocal: true, index: imageIndex),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
@@ -373,14 +365,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
height: 104,
|
||||
width: 104,
|
||||
child: Image.file(
|
||||
pickedFiles[i],
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: SizedBox(height: 104, width: 104, child: Image.file(pickedFiles[i], fit: BoxFit.cover)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -397,15 +382,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.gray50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
size: 20,
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
decoration: BoxDecoration(color: appTheme.gray50, shape: BoxShape.circle),
|
||||
child: const Icon(size: 20, Icons.close, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -427,7 +405,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
child: Ink(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
||||
child: Transform.rotate(
|
||||
angle: pi / 4,
|
||||
child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -449,7 +430,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
@@ -465,7 +448,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
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) {
|
||||
chatRoomCubit.sendMessage(
|
||||
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
|
||||
@@ -525,13 +528,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
fileName,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
child: Text(fileName, textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -579,13 +576,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||
// }
|
||||
},
|
||||
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 {
|
||||
@@ -600,13 +591,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.photo_library_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text('Галерея'),
|
||||
],
|
||||
),
|
||||
child: const Row(children: [Icon(Icons.photo_library_rounded), SizedBox(width: 8), Text('Галерея')]),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
@@ -643,20 +628,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.file_present_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text('Файлы'),
|
||||
],
|
||||
),
|
||||
child: const Row(children: [Icon(Icons.file_present_rounded), 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('Отмена')),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -665,7 +640,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: Text(DateFormat.yMMMMd('RU').format(date), style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500)),
|
||||
child: Text(
|
||||
DateFormat.yMMMMd('RU').format(date),
|
||||
style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.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 {
|
||||
final Author author;
|
||||
@@ -133,21 +135,45 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
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) {
|
||||
await sl<ChatRepository>().sendMessage(
|
||||
data: textEditingController.text.trim(),
|
||||
slug: widget.author.authorSlug,
|
||||
);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
});
|
||||
}
|
||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
if (context.mounted) {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Ink(
|
||||
@@ -175,20 +201,44 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
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();
|
||||
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||
if (image != null) {
|
||||
List<XFile>? images = await picker.pickMultiImage();
|
||||
if (images.isNotEmpty) {
|
||||
List<File> files = images.map((image) => File(image.path)).toList();
|
||||
await sl<ChatRepository>().sendMessage(
|
||||
files: [File(image.path)],
|
||||
files: files,
|
||||
slug: widget.author.authorSlug,
|
||||
);
|
||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
@@ -211,12 +261,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
slug: widget.author.authorSlug,
|
||||
);
|
||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
@@ -261,12 +313,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
slug: widget.author.authorSlug,
|
||||
);
|
||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
|
||||
@@ -5,8 +5,13 @@ import 'package:dismissible_page/dismissible_page.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/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ChatBubbleWidget extends StatelessWidget {
|
||||
@@ -17,6 +22,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
required this.isCurrentUser,
|
||||
required this.mediaData,
|
||||
required this.isRead,
|
||||
required this.messageState,
|
||||
required this.messageUuid,
|
||||
required this.userSlug
|
||||
});
|
||||
|
||||
final DateTime sendTime;
|
||||
@@ -24,7 +32,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
final bool isCurrentUser;
|
||||
final bool isRead;
|
||||
final List<dynamic> mediaData;
|
||||
|
||||
final MessageState messageState;
|
||||
final String messageUuid;
|
||||
final String userSlug;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
@@ -38,17 +48,33 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
),
|
||||
switch (messageState) {
|
||||
MessageState.loading => Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: SizedBox(width: 10, height: 10, child: CircularProgressIndicator(strokeWidth: 2, color: appTheme.blueA400)),
|
||||
),
|
||||
),
|
||||
|
||||
MessageState.sent => Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(sendTime.format('HH:mm', 'ru'), style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10)),
|
||||
),
|
||||
|
||||
MessageState.error => GestureDetector(
|
||||
onTap: () {
|
||||
showMessageActionsBottomSheet(
|
||||
context,
|
||||
resend: () {
|
||||
context.read<ChatRoomCubit>().resendMessage(uuid: messageUuid, userSlug: userSlug);
|
||||
},
|
||||
delete: () {
|
||||
context.read<ChatRoomCubit>().deleteMessage(messageUuid);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(padding: const EdgeInsets.only(right: 4, bottom: 4), child: SvgPicture.asset('assets/icons/error_icon.svg')),
|
||||
),
|
||||
},
|
||||
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
||||
decoration: BoxDecoration(
|
||||
@@ -87,33 +113,19 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||
}
|
||||
},
|
||||
leading: const Icon(
|
||||
Icons.file_copy_outlined,
|
||||
color: Colors.black,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.download_outlined,
|
||||
color: Colors.black,
|
||||
),
|
||||
leading: const Icon(Icons.file_copy_outlined, color: Colors.black),
|
||||
trailing: const Icon(Icons.download_outlined, color: Colors.black),
|
||||
title: Text(
|
||||
mediaData[index].toString().replaceAll('chat_files/', ''),
|
||||
maxLines: 2,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: const TextStyle(color: Colors.black, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: mediaData,
|
||||
index: index,
|
||||
isLocal: mediaData[index] is File ? true : false,
|
||||
),
|
||||
PhotoViewScreen(imageUrls: mediaData, index: index, isLocal: mediaData[index] is File ? true : false),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
@@ -121,11 +133,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: mediaData[index] is File
|
||||
? Image.file(mediaData[index])
|
||||
: CachedNetworkImage(
|
||||
imageUrl: '${mediaData[index]}',
|
||||
),
|
||||
child: mediaData[index] is File ? Image.file(mediaData[index]) : CachedNetworkImage(imageUrl: '${mediaData[index]}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -137,11 +145,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (mediaData.isNotEmpty && text.isNotEmpty) const SizedBox(height: 8),
|
||||
if (text.isNotEmpty)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
|
||||
),
|
||||
if (text.isNotEmpty) Text(text, style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -151,12 +155,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_action_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showMessageActionsBottomSheet(BuildContext context, {required void Function() resend, required void Function() delete}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700),
|
||||
child: MessageActionsBottomSheet(resend: resend, delete: delete),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MessageActionsBottomSheet extends StatefulWidget {
|
||||
const MessageActionsBottomSheet({super.key, required this.resend, required this.delete});
|
||||
final void Function() resend;
|
||||
final void Function() delete;
|
||||
|
||||
@override
|
||||
State<MessageActionsBottomSheet> createState() => _MessageActionsBottomSheet();
|
||||
}
|
||||
|
||||
class _MessageActionsBottomSheet extends State<MessageActionsBottomSheet> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 7),
|
||||
const BottomSheetHandlerBar(),
|
||||
BottomSheetActionWidget(text: s.resend, iconPath: 'assets/icons/resend.svg', onTap: widget.resend),
|
||||
const CustomDivider(),
|
||||
BottomSheetActionWidget(text: s.delete, iconPath: 'assets/icons/trash_can.svg', onTap: widget.delete, color: appTheme.red),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,24 +25,16 @@ class _LastStepScreenState extends State<LastStepScreen> {
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Последний шаг',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
child: Text('Последний шаг', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CheckboxListTile(
|
||||
value: checkboxState,
|
||||
title: const Text(
|
||||
'Я ознакомлен и принимаю договор публичной офёрты',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
height: 1,
|
||||
),
|
||||
title: Text(
|
||||
hidePlans
|
||||
? 'Я ознакомлен и принимаю политику конфиденциальности и защиту персональных данных'
|
||||
: 'Я ознакомлен и принимаю договор публичной офёрты',
|
||||
style: const TextStyle(fontSize: 18, height: 1),
|
||||
),
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
@@ -53,25 +45,14 @@ class _LastStepScreenState extends State<LastStepScreen> {
|
||||
activeColor: appTheme.blueA400,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
child: _divider(context),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: _divider(context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: TextButton(
|
||||
onPressed: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
|
||||
child: const Text(
|
||||
'Договор офёрты',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 16,
|
||||
color: Color(0xFF323F4B),
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '$baseUrl/docs/privacy.pdf' : '$baseUrl/oferta_bs.pdf')),
|
||||
child: Text(
|
||||
hidePlans ? 'Политика конфиденциальности и пользовательское соглашение' : 'Договор офёрты',
|
||||
style: const TextStyle(height: 1, fontSize: 16, color: Color(0xFF323F4B), decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -84,10 +65,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
|
||||
return Container(
|
||||
height: 0.4,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class ConsultationService {
|
||||
else if (state != null && stateComparison != null)
|
||||
'filters[last_state][${stateComparison.value}]': state,
|
||||
if (dateComparison != null && date != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||
'sort[]': 'created_at:desc',
|
||||
},
|
||||
);
|
||||
return ConsultationPaginationModel.fromJson(response.data);
|
||||
@@ -77,7 +78,8 @@ class ConsultationService {
|
||||
final response = await dio.get(
|
||||
'/api/finances/my_finances',
|
||||
queryParameters: {
|
||||
if (dateComparison != null && date != null) 'filters[end_date]': date,
|
||||
if (date != null && dateComparison != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||
'sort[]': 'start_date:desc',
|
||||
},
|
||||
);
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
@@ -96,7 +98,8 @@ class ConsultationService {
|
||||
final response = await dio.get(
|
||||
'/api/finances/my_purchases',
|
||||
queryParameters: {
|
||||
if (dateComparison != null && date != null) 'filters[start_date]': date,
|
||||
if (date != null && dateComparison != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||
'sort[]': 'start_date:desc',
|
||||
},
|
||||
);
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
|
||||
@@ -31,9 +31,7 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
||||
dateComparison: state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
|
||||
stateComparison: state.consultationFilterStatus == ConsultationFilterStatus.all
|
||||
? null
|
||||
: state.consultationFilterStatus == ConsultationFilterStatus.inProgress
|
||||
? FiltersComparisonEnum.notInArray
|
||||
: FiltersComparisonEnum.equal,
|
||||
: FiltersComparisonEnum.equal,
|
||||
);
|
||||
emit(state.copyWith(consultations: consultations, isLoading: false));
|
||||
} catch (e) {
|
||||
@@ -67,20 +65,17 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
||||
enum ConsultationFilterStatus {
|
||||
all,
|
||||
canceled,
|
||||
finished,
|
||||
inProgress;
|
||||
finished;
|
||||
|
||||
String get name => switch (this) {
|
||||
ConsultationFilterStatus.all => 'Все консультации',
|
||||
ConsultationFilterStatus.canceled => 'Отменён',
|
||||
ConsultationFilterStatus.finished => 'Завершён',
|
||||
ConsultationFilterStatus.inProgress => 'В процессе',
|
||||
};
|
||||
|
||||
String? get value => switch (this) {
|
||||
ConsultationFilterStatus.all => null,
|
||||
ConsultationFilterStatus.canceled => 'canceled_by_the_initiator',
|
||||
ConsultationFilterStatus.finished => 'charge_was_collected',
|
||||
ConsultationFilterStatus.inProgress => 'active',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ abstract class ConsultationsArchiveState with _$ConsultationsArchiveState {
|
||||
@Default([]) List<ConsultationModel> consultations,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(null) ConsultationFilterStatus? consultationFilterStatus,
|
||||
required StatisticModel statisticModel,
|
||||
}) = _ConsultationsArchiveState;
|
||||
|
||||
@@ -10,7 +10,7 @@ abstract class ConsultantFinancesState with _$ConsultantFinancesState {
|
||||
@Default([]) List<ConsultantFinanceModel> financesModels,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
}) = _ConsultantFinancesState;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ abstract class UserPurchasesState with _$UserPurchasesState {
|
||||
@Default([]) List<UserPurchasesModel> purchasesModels,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
}) = _UserPurchasesState;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
|
||||
if (value == null) return;
|
||||
context.read<ConsultantFinancesCubit>().setFinancesPeriod(value);
|
||||
},
|
||||
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
|
||||
value: state.periodFilterStatus ?? PeriodFilterStatus.all,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (state.financesModels.isNotEmpty)
|
||||
@@ -129,7 +129,7 @@ class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
|
||||
);
|
||||
}),
|
||||
Text(
|
||||
state.financesModels[index].startDate.format('yyyy.MM.dd'),
|
||||
state.financesModels[index].startDate.format('dd.MM.yy'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF6D6F71),
|
||||
height: 1,
|
||||
|
||||
@@ -195,7 +195,7 @@ class _ConsultationsArchiveScreenState extends State<ConsultationsArchiveScreen>
|
||||
),
|
||||
),
|
||||
Text(
|
||||
state.consultations[index].startDate.format('yyyy.MM.dd'),
|
||||
state.consultations[index].startDate.format('dd.MM.yy'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF6D6F71),
|
||||
height: 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
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';
|
||||
@@ -14,6 +15,7 @@ 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:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class MenuScreen extends StatelessWidget {
|
||||
final ProfileModel profileModel;
|
||||
@@ -67,6 +69,8 @@ class MenuScreen extends StatelessWidget {
|
||||
// ),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||
const SizedBox(height: 10),
|
||||
const AppInfo(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -124,3 +128,36 @@ class MenuScreen extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class AppInfo extends StatefulWidget {
|
||||
const AppInfo({super.key});
|
||||
|
||||
@override
|
||||
State<AppInfo> createState() => _AppInfoState();
|
||||
}
|
||||
|
||||
class _AppInfoState extends State<AppInfo> {
|
||||
String version = '';
|
||||
String buildNumber = '';
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
|
||||
setState(() {
|
||||
version = packageInfo.version;
|
||||
buildNumber = packageInfo.buildNumber;
|
||||
});
|
||||
});
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 16),
|
||||
child: Text('Версия приложения: $version ($buildNumber)', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@@ -570,6 +861,16 @@ enum NotificationType {
|
||||
commentReplied,
|
||||
scoreAdded,
|
||||
entityCommented,
|
||||
// Дополнительные типы уведомлений
|
||||
planSubscriptionExpired,
|
||||
planSubscriptionRenewed,
|
||||
consultantAssigned,
|
||||
consultantUnassigned,
|
||||
paymentFailed,
|
||||
paymentSuccessful,
|
||||
goalAchieved,
|
||||
workoutReminder,
|
||||
mealReminder,
|
||||
unknown;
|
||||
|
||||
static NotificationType getTypeFromString(String type) => switch (type) {
|
||||
@@ -593,6 +894,16 @@ enum NotificationType {
|
||||
'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied,
|
||||
'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded,
|
||||
'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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -113,6 +113,16 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
||||
NotificationType.commentReplied => CommentReplied.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.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),
|
||||
};
|
||||
} catch (e) {
|
||||
|
||||
@@ -47,6 +47,16 @@ class NotificationWidget extends StatelessWidget {
|
||||
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
|
||||
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
|
||||
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),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,61 @@ class NotificationsService {
|
||||
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.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)),
|
||||
};
|
||||
} catch (e) {
|
||||
|
||||
@@ -19,6 +19,7 @@ abstract class ProfileModel with _$ProfileModel {
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@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_user", fromJson: parseBool, defaultValue: false) required bool isBlockedByUser,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required String slug,
|
||||
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
||||
|
||||
@@ -93,6 +93,39 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
}
|
||||
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);
|
||||
} else if (state is ProfileStateError) {
|
||||
return Scaffold(
|
||||
@@ -174,10 +207,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
_subscriptionsAndSubscribers(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) _education(state),
|
||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _diplomasAndCertificates(state),
|
||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans) _activePlans(state),
|
||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _socialLinks(state),
|
||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _education(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 && !state.profile.isBlockedByMe) _activePlans(state),
|
||||
_blogPosts(state),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -51,9 +51,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
builder: (BuildContext context, SettingsState state) {
|
||||
if (state is SettingsStateLoaded) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Настройки'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Настройки')),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
@@ -64,8 +62,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
const SizedBox(height: 12),
|
||||
_notifications(state),
|
||||
const SizedBox(height: 12),
|
||||
_aboutEatvio(state),
|
||||
const SizedBox(height: 12),
|
||||
if (!hidePlans) ...[_aboutEatvio(state), const SizedBox(height: 12)],
|
||||
_becomeConsultantButton(state),
|
||||
_deleteAccountButton(state, settingsCubit),
|
||||
],
|
||||
@@ -76,16 +73,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
if (state is SettingsStateError) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Настройки'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Настройки')),
|
||||
body: DataErrorWidget(onReload: () => settingsCubit.init()),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Настройки'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Настройки')),
|
||||
body: const Loader(),
|
||||
);
|
||||
},
|
||||
@@ -101,22 +94,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Дополнительаная информация',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
color: Colors.grey,
|
||||
fontFamily: 'Mulish',
|
||||
),
|
||||
style: TextStyle(fontSize: 16, height: 1, color: Colors.grey, fontFamily: 'Mulish'),
|
||||
),
|
||||
),
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'generalInfoScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Общая информация',
|
||||
),
|
||||
@@ -124,11 +108,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'physicalFormScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Физическая форма',
|
||||
),
|
||||
@@ -136,11 +116,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'pressureScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Давление',
|
||||
),
|
||||
@@ -148,11 +124,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'otherInfoScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Прочая информация',
|
||||
),
|
||||
@@ -160,31 +132,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'physicalActivityScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Физическая нагрузка',
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'forgetPasswordScreen',
|
||||
extra: true,
|
||||
),
|
||||
title: 'Сменить пароль',
|
||||
),
|
||||
_tile(onTap: () => GoRouter.of(context).pushNamed('forgetPasswordScreen', extra: true), title: 'Сменить пароль'),
|
||||
_divider(context),
|
||||
_tile(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'changeEmailScreen',
|
||||
extra: SettingsEntity(
|
||||
profileSettingsModel: state.profileSettings,
|
||||
profileDetailsModel: state.profileDetails,
|
||||
settingsCubit: settingsCubit,
|
||||
),
|
||||
extra: SettingsEntity(profileSettingsModel: state.profileSettings, profileDetailsModel: state.profileDetails, settingsCubit: settingsCubit),
|
||||
),
|
||||
title: 'Сменить Email',
|
||||
),
|
||||
@@ -198,14 +156,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Уведомления',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
child: Text('Уведомления', style: TextStyle(fontSize: 16, height: 1, color: Colors.grey)),
|
||||
),
|
||||
_tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -213,11 +164,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
updatesNotificationsSwitchState = !updatesNotificationsSwitchState;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
newUpdatesNotifications: updatesNotificationsSwitchState,
|
||||
),
|
||||
);
|
||||
settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(newUpdatesNotifications: updatesNotificationsSwitchState));
|
||||
},
|
||||
title: 'Уведомление о новых обновлениях',
|
||||
trailing: CupertinoSwitch(
|
||||
@@ -228,9 +175,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
updatesNotificationsSwitchState = newValue;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
newUpdatesNotifications: updatesNotificationsSwitchState,
|
||||
),
|
||||
profileSettingsModel: state.profileSettings.copyWith(newUpdatesNotifications: updatesNotificationsSwitchState),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -243,9 +188,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
fillDiaryNotificationsSwitchState = !fillDiaryNotificationsSwitchState;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
diaryReminderNotifications: fillDiaryNotificationsSwitchState,
|
||||
),
|
||||
profileSettingsModel: state.profileSettings.copyWith(diaryReminderNotifications: fillDiaryNotificationsSwitchState),
|
||||
);
|
||||
},
|
||||
title: 'Напоминание о заполнении дневника',
|
||||
@@ -257,9 +200,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
fillDiaryNotificationsSwitchState = newValue;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
diaryReminderNotifications: fillDiaryNotificationsSwitchState,
|
||||
),
|
||||
profileSettingsModel: state.profileSettings.copyWith(diaryReminderNotifications: fillDiaryNotificationsSwitchState),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -271,11 +212,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
moodSwitchState = !moodSwitchState;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
isMoodFillable: moodSwitchState,
|
||||
),
|
||||
);
|
||||
settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isMoodFillable: moodSwitchState));
|
||||
},
|
||||
title: 'Указывать “настроение” в заполнение дневника',
|
||||
trailing: CupertinoSwitch(
|
||||
@@ -285,11 +222,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
moodSwitchState = newValue;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
isMoodFillable: moodSwitchState,
|
||||
),
|
||||
);
|
||||
settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isMoodFillable: moodSwitchState));
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -300,11 +233,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
bedTimeSwitchState = !bedTimeSwitchState;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
isBedtimeFillable: bedTimeSwitchState,
|
||||
),
|
||||
);
|
||||
settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isBedtimeFillable: bedTimeSwitchState));
|
||||
},
|
||||
title: 'Указывать “отход ко сну” в заполнение дневника',
|
||||
trailing: CupertinoSwitch(
|
||||
@@ -314,11 +243,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
bedTimeSwitchState = newValue;
|
||||
});
|
||||
settingsCubit.updateProfileSettings(
|
||||
profileSettingsModel: state.profileSettings.copyWith(
|
||||
isBedtimeFillable: bedTimeSwitchState,
|
||||
),
|
||||
);
|
||||
settingsCubit.updateProfileSettings(profileSettingsModel: state.profileSettings.copyWith(isBedtimeFillable: bedTimeSwitchState));
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -332,24 +257,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'О Eatvio',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
_tile(
|
||||
title: 'О компании',
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/ru/company-details')),
|
||||
child: Text('О Eatvio', style: TextStyle(fontSize: 16, height: 1, color: Colors.grey)),
|
||||
),
|
||||
_tile(title: 'О компании', onTap: () async => await launchUrl(Uri.parse('$baseUrl/ru/company-details'))),
|
||||
_divider(context),
|
||||
_tile(
|
||||
title: 'Пользовательское соглашение',
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
|
||||
),
|
||||
_tile(title: 'Пользовательское соглашение', onTap: () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf'))),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -364,19 +276,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Стать консультантом',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
height: 1,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: Text('Стать консультантом', style: TextStyle(color: Colors.white, height: 1, fontSize: 18)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -388,19 +290,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color(0xFFEAEAEA),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Данные на проверке',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
height: 1,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -413,19 +305,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color(0x33FFCC00),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0x33FFCC00)),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Заявка обработана',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
height: 1,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: Text('Заявка обработана', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -444,57 +326,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: DeleteAccountBottomSheet(
|
||||
cubit: settingsCubit,
|
||||
),
|
||||
child: DeleteAccountBottomSheet(cubit: settingsCubit),
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: MediaQuery.of(context).size.width - 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color(0xFFEAEAEA),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Удалить аккаунт',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
height: 1,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: Text('Удалить аккаунт', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ListTile _tile({
|
||||
required String title,
|
||||
required Function() onTap,
|
||||
Widget? trailing,
|
||||
EdgeInsets? padding,
|
||||
}) {
|
||||
ListTile _tile({required String title, required Function() onTap, Widget? trailing, EdgeInsets? padding}) {
|
||||
return ListTile(
|
||||
contentPadding: padding,
|
||||
onTap: onTap,
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
trailing: trailing ??
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 12,
|
||||
color: Colors.black38,
|
||||
),
|
||||
title: Text(title, 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(
|
||||
height: 0.4,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -530,33 +380,23 @@ class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(s.accountDeleting, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.youShureYouWantToDeleteAccount,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
s.itCantBeRecovered,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CustomOutlinedButton(
|
||||
text: s.delete,
|
||||
height: 52,
|
||||
@@ -573,9 +413,7 @@ class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
|
||||
showErrorDialog(context, description: s.cannotDeleteAccount);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -69,24 +69,17 @@ class SupportScreen extends StatelessWidget {
|
||||
child: Text('Правовые документы', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/public_offer.pdf')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text('Публичная офёрта', style: TextStyle(fontSize: 16)),
|
||||
if (!hidePlans) ...[
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text('Политика конфиденциальности', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text('Политика конфиденциальности', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/company-details')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
|
||||
@@ -634,6 +634,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Снять с публикации",
|
||||
),
|
||||
"reply": MessageLookupByLibrary.simpleMessage("Ответить"),
|
||||
"resend": MessageLookupByLibrary.simpleMessage("Отправить повторно"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"send": MessageLookupByLibrary.simpleMessage("Отправить"),
|
||||
"send_complain": MessageLookupByLibrary.simpleMessage("Отправить жалобу"),
|
||||
|
||||
@@ -3459,6 +3459,16 @@ class S {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Отправить повторно`
|
||||
String get resend {
|
||||
return Intl.message(
|
||||
'Отправить повторно',
|
||||
name: 'resend',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
"youShureYouWantToDeleteAccount": "Вы уверены, что хотите удалить аккаунт?",
|
||||
"itCantBeRecovered": "Его нельзя будет восстановить",
|
||||
"delete": "Удалить",
|
||||
"cannotDeleteAccount": "Не получилось удалить ваш аккаунт"
|
||||
|
||||
"cannotDeleteAccount": "Не получилось удалить ваш аккаунт",
|
||||
"resend": "Отправить повторно"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.2.8+68
|
||||
version: 1.2.9+69
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
@@ -77,6 +77,7 @@ dependencies:
|
||||
permission_handler: ^12.0.1
|
||||
qr_code_scanner_plus: ^2.0.10+1
|
||||
flutter_dotenv: ^5.1.0
|
||||
uuid: ^4.5.1
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.6.0
|
||||
|
||||
@@ -17,6 +17,7 @@ void main() {
|
||||
(_) async => await Future.value(
|
||||
const ProfileModel(
|
||||
id: 0,
|
||||
isBlockedByUser: false,
|
||||
name: 'Alexander Avanesyan',
|
||||
email: 'mso7nueh@mail.ru',
|
||||
phoneConfirmationState: '',
|
||||
|
||||
Reference in New Issue
Block a user