diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 646293d0..156d8566 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -22,6 +22,7 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; +import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.dart'; import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; @@ -63,7 +64,17 @@ class AppRouter { } return null; }, - builder: (context, state) => AuthScreen.builder(context), + builder: (context, state) { + final bool needLogout; + if (state.extra == null) { + needLogout = false; + } else { + needLogout = true; + } + return AuthScreen( + needLogout: needLogout, + ); + }, routes: [ GoRoute( path: 'registration-screen', @@ -125,6 +136,11 @@ class AppRouter { ); }, ), + GoRoute( + path: '/glossary-screen', + name: '/glossaryScreen', + builder: (context, state) => GlossaryScreen(), + ), GoRoute( path: '/about-recipes-screen', name: 'aboutRecipesScreen', diff --git a/lib/core/const.dart b/lib/core/const.dart index 4e8b3650..dfeae1fc 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,5 +1,5 @@ -// String baseUrl = 'https://eatvio.ru'; -String baseUrl = 'https://dev2.eatvio.com'; +String baseUrl = 'https://eatvio.ru'; +// String baseUrl = 'https://dev2.eatvio.com'; const List durationValues = [7, 14, 21]; diff --git a/lib/core/credentials_storage.dart b/lib/core/credentials_storage.dart new file mode 100644 index 00000000..6cf97c90 --- /dev/null +++ b/lib/core/credentials_storage.dart @@ -0,0 +1,32 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +abstract class CredentialsStorage { + static Future haveCredentialsInStorage() async { + try { + if (await FlutterSecureStorage().read(key: 'email') == null || await FlutterSecureStorage().read(key: 'password') == null) { + return false; + } else { + return true; + } + } catch (e) { + return false; + } + } + + static Future> getCredentialsFromStorage() async { + return { + 'email': (await FlutterSecureStorage().read(key: 'email'))!, + 'password': (await FlutterSecureStorage().read(key: 'password'))!, + }; + } + + static saveCredentials(String email, String password) async { + await FlutterSecureStorage().write(key: 'email', value: email); + await FlutterSecureStorage().write(key: 'password', value: password); + } + + static removeCredentials() async { + await FlutterSecureStorage().delete(key: 'email'); + await FlutterSecureStorage().delete(key: 'password'); + } +} diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index e258d702..c0377a79 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -64,6 +64,7 @@ import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/se import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart'; import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profilePlans; @@ -155,4 +156,5 @@ Future init() async { ), ), ); + sl.registerLazySingleton(() async => await FlutterSecureStorage()); } diff --git a/lib/core/token_storage.dart b/lib/core/token_storage.dart index 4a8bb21d..9332ceb5 100644 --- a/lib/core/token_storage.dart +++ b/lib/core/token_storage.dart @@ -7,7 +7,7 @@ abstract class TokenStorage { sl().options.headers['Authorization'] = 'Bearer $token'; } - static haveTokenInStorage() async { + static Future haveTokenInStorage() async { try { if (await FlutterSecureStorage().read(key: 'token') == null) { return false; diff --git a/lib/features/auth/auth_screen/auth_screen.dart b/lib/features/auth/auth_screen/auth_screen.dart index 914398f1..3fbc57e3 100644 --- a/lib/features/auth/auth_screen/auth_screen.dart +++ b/lib/features/auth/auth_screen/auth_screen.dart @@ -1,41 +1,22 @@ import 'dart:convert'; - -import 'package:dio/dio.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/credentials_storage.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/core/utils/image_constant.dart'; -import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/auth/const/strings.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; -import 'package:eatvio_app/generated/l10n.dart'; -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; -import 'package:url_launcher/url_launcher.dart'; -import 'package:validators/validators.dart'; import 'package:webview_flutter/webview_flutter.dart'; -import 'bloc/auth_bloc.dart'; -import 'models/auth_model.dart'; - class AuthScreen extends StatefulWidget { - static Widget builder(BuildContext context) { - return BlocProvider( - create: (context) => AuthBloc( - AuthState( - authModelObj: AuthModel(), - ), - )..add( - AuthInitialEvent(), - ), - child: AuthScreen(), - ); - } + final bool needLogout; + + const AuthScreen({ + this.needLogout = false, + }); @override State createState() => _AuthScreenState(); @@ -47,6 +28,16 @@ class _AuthScreenState extends State { final baseUrlController = TextEditingController(); late final WebViewController _controller; + String email = ''; + String password = ''; + + Future logout() async { + await CredentialsStorage.removeCredentials(); + await _controller.runJavaScript(''' + document.querySelector('.header-list__link button[type="submit"]').click(); + '''); + } + @override void initState() { super.initState(); @@ -56,42 +47,73 @@ class _AuthScreenState extends State { ..addJavaScriptChannel( 'FlutterChannel', onMessageReceived: (JavaScriptMessage message) { - // Handle messages from JavaScript final data = jsonDecode(message.message); - final email = data['email']; - final password = data['password']; - print('Email: $email, Password: $password'); + email = data['email']; + password = data['password']; }, ) ..setNavigationDelegate( NavigationDelegate( - onPageFinished: (String url) async { - await _controller.runJavaScript(''' - function initializeListeners() { - const emailInput = document.getElementById("inputEmail_main"); - const passwordInput = document.getElementById("inputPassword_main"); - - if (emailInput && passwordInput) { - function sendToFlutter() { - const email = emailInput.value; - const password = passwordInput.value; - const data = JSON.stringify({ email: email, password: password }); - if (window.FlutterChannel) { - window.FlutterChannel.postMessage(data); - } + onNavigationRequest: (NavigationRequest navigationRequest) async { + if (navigationRequest.url.contains('profile')) { + if (await CredentialsStorage.haveCredentialsInStorage()) { + email = (await CredentialsStorage.getCredentialsFromStorage())['email']!; + password = (await CredentialsStorage.getCredentialsFromStorage())['password']!; + } else { + if (email.isNotEmpty && password.isNotEmpty && !(await TokenStorage.haveTokenInStorage())) { + await CredentialsStorage.saveCredentials(email, password); + try { + final Map credentials = { + 'email': email, + 'password': password, + }; + final response = await http.post( + Uri.parse(baseUrl + '/api/login'), + headers: {'Content-Type': 'application/json'}, + body: json.encode(credentials), + encoding: utf8, + ); + var res = await jsonDecode(response.body); + final token = res['token']; + await TokenStorage.saveTokenInStorage(token); + GoRouter.of(context).go('/my-profile-screen'); + await sl().getMyProfile(); + } catch (e) { + print(e); } - - emailInput.addEventListener("input", sendToFlutter); - passwordInput.addEventListener("input", sendToFlutter); } else { - console.error("Email or Password input not found."); + await logout(); } } - - // Ensure DOM is loaded before attaching listeners - document.addEventListener("DOMContentLoaded", initializeListeners); - initializeListeners(); - '''); + } + return NavigationDecision.navigate; + }, + onPageStarted: (String url) async { + final bool tokenInStorage = await TokenStorage.haveTokenInStorage(); + final bool credentialsInStorage = await CredentialsStorage.haveCredentialsInStorage(); + if (tokenInStorage && credentialsInStorage) { + GoRouter.of(context).go('/my-profile-screen'); + } else if (!tokenInStorage && credentialsInStorage) { + final credentialsFromStorage = await CredentialsStorage.getCredentialsFromStorage(); + final Map credentials = { + 'email': credentialsFromStorage['email']!, + 'password': credentialsFromStorage['password']!, + }; + final response = await http.post( + Uri.parse(baseUrl + '/api/login'), + headers: {'Content-Type': 'application/json'}, + body: json.encode(credentials), + encoding: utf8, + ); + var res = await jsonDecode(response.body); + final token = res['token']; + await TokenStorage.saveTokenInStorage(token); + GoRouter.of(context).go('/my-profile-screen'); + await sl().getMyProfile(); + } + }, + onPageFinished: (String url) async { + await _controller.runJavaScript(jsChannelAuthScript); }, ), ) @@ -101,269 +123,13 @@ class _AuthScreenState extends State { @override Widget build(BuildContext context) { FlutterNativeSplash.remove(); - final s = S.of(context); - - // return Scaffold( - // appBar: AppBar( - // toolbarHeight: 0, - // ), - // body: WebViewWidget(controller: _controller), - // ); - return Scaffold( appBar: AppBar( - centerTitle: true, - title: GestureDetector( - onLongPress: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('Base url без https:// и /api'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomTextFormField( - controller: baseUrlController, - hintText: 'dev2.eatvio.com', - ), - const SizedBox(height: 12), - InkWell( - onTap: () async { - baseUrl = 'https://${baseUrlController.text}'; - sl().options.baseUrl = baseUrl; - }, - borderRadius: BorderRadius.circular(20), - child: Ink( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), - alignment: Alignment.center, - child: Text( - 'Сохранить', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ], - ), - ), - ); - }, - child: SvgPicture.asset( - ImageConstant.imgLogoEatvio, - height: 70, - fit: BoxFit.contain, - ), - ), + toolbarHeight: 0, ), body: SafeArea( - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(s.email), - const SizedBox(height: 4), - BlocSelector( - selector: (state) => state.emailController, - builder: (context, emailController) { - return CustomTextFormField( - controller: emailController, - textInputAction: TextInputAction.next, - keyboardType: TextInputType.emailAddress, - textInputType: TextInputType.emailAddress, - hintText: 'Введите email', - ); - }, - ), - const SizedBox(height: 16), - Text(s.password), - const SizedBox(height: 4), - BlocSelector( - selector: (state) => state.passwordController, - builder: (context, passwordController) { - return CustomTextFormField( - obscureText: hidePassword, - controller: passwordController, - hintText: 'Введите пароль', - suffix: IconButton( - onPressed: () { - setState(() { - hidePassword = !hidePassword; - }); - }, - icon: Icon( - hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, - color: Colors.grey, - ), - ), - ); - }, - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Нет аккаунта? '), - GestureDetector( - onTap: () => GoRouter.of(context).goNamed('registrationScreen'), - child: Text( - 'Зарегистрироваться', - style: TextStyle( - color: appTheme.blueA400, - decorationColor: appTheme.blueA400, - decoration: TextDecoration.underline, - ), - ), - ), - ], - ), - Spacer(), - Center( - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: 'Нажимая "Войти", Вы соглашаетесь \n', - style: TextStyle( - fontSize: 12, - color: Colors.black, - ), - children: [ - TextSpan(text: 'c '), - 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')), - ), - ], - ), - ), - ), - const SizedBox(height: 16), - BlocBuilder( - builder: (context, state) { - return Container( - height: 48, - width: MediaQuery.of(context).size.width - 32, - child: FloatingActionButton.extended( - heroTag: 'authButton', - highlightElevation: 0, - elevation: 0, - focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: appTheme.blueA400, - onPressed: onTapAuth(state, context), - label: Text( - 'Войти', - style: theme.textTheme.titleSmall!.copyWith(color: Colors.white), - ), - ), - ); - }, - ), - ], - ), - ), + child: WebViewWidget(controller: _controller), ), ); } - - onTapAuth(AuthState state, BuildContext context) { - final s = S.of(context); - return () async { - List errors = validateFields(state.emailController!.text, state.passwordController!.text); - if (errors.isEmpty) { - final Map credentials = { - 'email': state.emailController!.text, - 'password': state.passwordController!.text, - }; - final response = await http.post( - Uri.parse(baseUrl + '/api/login'), - headers: {'Content-Type': 'application/json'}, - body: json.encode(credentials), - encoding: utf8, - ); - - if (response.statusCode == 200) { - var res = await jsonDecode(response.body); - final token = res['token']; - await TokenStorage.saveTokenInStorage(token); - GoRouter.of(context).go('/my-profile-screen'); - sl().getMyProfile(); - } else { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('Ошибка'), - content: Text(s.wrong_credentials), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text('OK'), - ), - ], - ), - ); - } - } else { - String errorMessage = ''; - - for (var error in errors) { - errorMessage += error + '\n\n'; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('Ошибка'), - content: Text(errorMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text('OK'), - ), - ], - ), - ); - } - }; - } - - List validateFields(String email, String password) { - final s = S.of(context); - - List errors = []; - - if (!isEmail(email)) { - errors.add(s.email_invalid); - } - - if (email.isEmpty) { - errors.add(s.email_empty); - } - - if (password.isEmpty) { - errors.add(s.password_empty); - } - - if (password.length < 6) { - errors.add(s.password_short); - } - - return errors; - } } diff --git a/lib/features/auth/auth_screen/bloc/auth_bloc.dart b/lib/features/auth/auth_screen/bloc/auth_bloc.dart deleted file mode 100644 index f2df813c..00000000 --- a/lib/features/auth/auth_screen/bloc/auth_bloc.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:eatvio_app/features/auth/auth_screen/models/auth_model.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'auth_event.dart'; -part 'auth_state.dart'; - -/// A bloc that manages the state of a Auth according to the event that is dispatched to it. -class AuthBloc extends Bloc { - AuthBloc(AuthState initialState) : super(initialState) { - on(_onInitialize); - } - - _onInitialize( - AuthInitialEvent event, - Emitter emit, - ) async { - emit( - state.copyWith( - emailController: TextEditingController(), - passwordController: TextEditingController(), - ), - ); - } -} diff --git a/lib/features/auth/auth_screen/bloc/auth_event.dart b/lib/features/auth/auth_screen/bloc/auth_event.dart deleted file mode 100644 index a395a45b..00000000 --- a/lib/features/auth/auth_screen/bloc/auth_event.dart +++ /dev/null @@ -1,16 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'auth_bloc.dart'; - -/// Abstract class for all events that can be dispatched from the -///Credentials widget. -/// -/// Events must be immutable and implement the [Equatable] interface. -@immutable -abstract class AuthEvent extends Equatable {} - -/// Event that is dispatched when the Credentials widget is first created. -class AuthInitialEvent extends AuthEvent { - @override - List get props => []; -} diff --git a/lib/features/auth/auth_screen/bloc/auth_state.dart b/lib/features/auth/auth_screen/bloc/auth_state.dart deleted file mode 100644 index 86e8e3b0..00000000 --- a/lib/features/auth/auth_screen/bloc/auth_state.dart +++ /dev/null @@ -1,37 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'auth_bloc.dart'; - -/// Represents the state of Auth in the application. -class AuthState extends Equatable { - AuthState({ - this.emailController, - this.passwordController, - this.authModelObj, - }); - - TextEditingController? emailController; - - TextEditingController? passwordController; - - AuthModel? authModelObj; - - @override - List get props => [ - emailController, - passwordController, - authModelObj, - ]; - - AuthState copyWith({ - TextEditingController? emailController, - TextEditingController? passwordController, - AuthModel? authModelObj, - }) { - return AuthState( - emailController: emailController ?? this.emailController, - passwordController: passwordController ?? this.passwordController, - authModelObj: authModelObj ?? this.authModelObj, - ); - } -} diff --git a/lib/features/auth/auth_screen/models/auth_model.dart b/lib/features/auth/auth_screen/models/auth_model.dart deleted file mode 100644 index 829b38c8..00000000 --- a/lib/features/auth/auth_screen/models/auth_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -// ignore_for_file: must_be_immutable - -import 'package:equatable/equatable.dart'; - -/// This class defines the variables used in the [auth_screen], -/// and is typically used to hold data that is passed between different parts of the application. -class AuthModel extends Equatable { - AuthModel(); - - AuthModel copyWith() { - return AuthModel(); - } - - @override - List get props => []; -} diff --git a/lib/features/auth/const/strings.dart b/lib/features/auth/const/strings.dart new file mode 100644 index 00000000..66d05c4d --- /dev/null +++ b/lib/features/auth/const/strings.dart @@ -0,0 +1,22 @@ +const String jsChannelAuthScript = ''' + function initializeListeners() { + const emailInput = document.getElementById("inputEmail_main"); + const passwordInput = document.getElementById("inputPassword_main"); + if (emailInput && passwordInput) { + function sendToFlutter() { + const email = emailInput.value; + const password = passwordInput.value; + const data = JSON.stringify({ email: email, password: password }); + if (window.FlutterChannel) { + window.FlutterChannel.postMessage(data); + } + } + emailInput.addEventListener("input", sendToFlutter); + passwordInput.addEventListener("input", sendToFlutter); + } else { + console.error("Email or Password input not found."); + } + } + document.addEventListener("DOMContentLoaded", initializeListeners); + initializeListeners(); + '''; diff --git a/lib/features/glossary/presentation/pages/glossary_screen.dart b/lib/features/glossary/presentation/pages/glossary_screen.dart new file mode 100644 index 00000000..97d59810 --- /dev/null +++ b/lib/features/glossary/presentation/pages/glossary_screen.dart @@ -0,0 +1,33 @@ +import 'package:eatvio_app/core/const.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class GlossaryScreen extends StatefulWidget { + const GlossaryScreen({super.key}); + + @override + State createState() => _GlossaryScreenState(); +} + +class _GlossaryScreenState extends State { + late final WebViewController _controller; + + @override + void initState() { + super.initState(); + + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(baseUrl + '/product_nutritional_value')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Глоссарий'), + ), + body: WebViewWidget(controller: _controller), + ); + } +} diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index a1102186..cfc2e8a1 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -127,7 +127,7 @@ class MenuScreen extends StatelessWidget { } catch (e) { } finally { await TokenStorage.removeTokenInStorage(); - GoRouter.of(context).go('/auth-screen'); + GoRouter.of(context).go('/auth-screen', extra: true); } }; } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 7fbaf5bc..2bcea0e1 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -53,6 +53,7 @@ class MessageLookup extends MessageLookupByLibrary { "bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"), "by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"), "calories": MessageLookupByLibrary.simpleMessage("Калории"), + "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), "carb_type_desc": MessageLookupByLibrary.simpleMessage( "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.", @@ -86,10 +87,14 @@ class MessageLookup extends MessageLookupByLibrary { "comments": MessageLookupByLibrary.simpleMessage("Комментарии"), "comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"), "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), + "confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"), "continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"), "continue_": MessageLookupByLibrary.simpleMessage("Продолжить"), "copied": MessageLookupByLibrary.simpleMessage("cкопирован"), "copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"), + "could_not_cancel_diet": MessageLookupByLibrary.simpleMessage( + "Не удалось отменить диету", + ), "country": MessageLookupByLibrary.simpleMessage("Страна"), "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), "customer_price": MessageLookupByLibrary.simpleMessage( @@ -103,6 +108,7 @@ class MessageLookup extends MessageLookupByLibrary { "diet": MessageLookupByLibrary.simpleMessage("Диета"), "diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"), "diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"), + "diet_started": MessageLookupByLibrary.simpleMessage("Диета уже начата"), "diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"), "diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"), "do_you_have_hypertension_tendency": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 68b55718..62d2834b 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1799,6 +1799,41 @@ class S { args: [], ); } + + /// `Диета уже начата` + String get diet_started { + return Intl.message( + 'Диета уже начата', + name: 'diet_started', + desc: '', + args: [], + ); + } + + /// `Отменить диету?` + String get cancel_diet { + return Intl.message( + 'Отменить диету?', + name: 'cancel_diet', + desc: '', + args: [], + ); + } + + /// `Подтвердить` + String get confirm { + return Intl.message('Подтвердить', name: 'confirm', desc: '', args: []); + } + + /// `Не удалось отменить диету` + String get could_not_cancel_diet { + return Intl.message( + 'Не удалось отменить диету', + name: 'could_not_cancel_diet', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/main.dart b/lib/main.dart index ea6fb543..5b223f8a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,12 +14,19 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; Future main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await di.init(); - runApp(MyApp()); + await SentryFlutter.init( + (options) { + options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2'; + options.tracesSampleRate = 1.0; + }, + appRunner: () => runApp(MyApp()), + ); FlutterNativeSplash.remove(); } diff --git a/pubspec.yaml b/pubspec.yaml index 9ff64948..dbff2756 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,14 +23,14 @@ dependencies: validators: ^3.0.0 path_provider: ^2.1.5 carousel_slider_plus: ^7.1.0 - flutter_form_builder: ^7.0.0-alpha.3 + flutter_form_builder: ^9.7.0 image_picker: ^1.1.2 flutter_typeahead: ^5.2.0 - go_router: ^14.7.1 + go_router: ^14.7.2 keyboard_dismisser: ^3.0.0 retrofit: ^4.4.2 get_it: ^8.0.3 - dio: ^5.7.0 + dio: ^5.8.0+1 dart_pusher_channels: ^1.2.3 photo_view: ^0.15.0 dismissible_page: ^1.0.2 @@ -38,14 +38,14 @@ dependencies: easy_debounce: ^2.0.3 badges: ^3.1.2 google_fonts: ^6.2.1 - syncfusion_flutter_charts: ^28.1.38 - syncfusion_flutter_gauges: ^28.1.38 + syncfusion_flutter_charts: ^28.2.3 + syncfusion_flutter_gauges: ^28.2.3 table_calendar: ^3.2.0 flutter_keyboard_visibility: ^6.0.0 share_plus: ^10.1.4 flutter_html: ^3.0.0-beta.2 - file_picker: ^8.1.7 - audioplayers: ^6.1.0 + file_picker: ^8.3.1 + audioplayers: ^6.1.1 intl: ^0.20.2 rive: ^0.13.20 mask_text_input_formatter: ^2.9.0 @@ -59,6 +59,7 @@ dependencies: http: ^1.3.0 flutter_native_splash: ^2.4.4 webview_flutter: ^4.10.0 + sentry_flutter: ^8.13.0 connectivity_plus: ^6.1.2 dev_dependencies: