// ignore_for_file: unused_element, duplicate_ignore import 'dart:async'; import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/websockets/ws_manager.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/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.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:url_launcher/url_launcher.dart'; import 'package:eatvio_app/features/auth/sdk/social_auth_facade.dart'; class AuthScreen extends StatefulWidget { const AuthScreen({super.key}); @override State createState() => _AuthScreenState(); } class _AuthScreenState extends State { final emailController = TextEditingController(); final passwordController = TextEditingController(); bool checkboxState = false; bool hidePassword = true; final authCubit = sl(); late StreamSubscription keyboardSubscription; bool isFocused = false; int tapsCount = 0; @override void initState() { super.initState(); var keyboardVisibilityController = KeyboardVisibilityController(); keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) { if (mounted) { setState(() { isFocused = visible; }); } }); } @override void dispose() { keyboardSubscription.cancel(); super.dispose(); } Future _reconnectWebSocket(BuildContext context) async { try { await sl().clear(); await sl().initialize(); } catch (e) { rethrow; } if (context.mounted) { try { await sl().getMyProfile(); await sl().getChatRoomsWithLoading(); await sl().connectToWebSocket(); await sl().getNotifications(); await sl().connectToWebSocket(); await sl().registerPushToken(); showSuccessToast('Успешный вход'); } catch (e) { rethrow; } } AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false}); } @override Widget build(BuildContext context) { var shortestSide = MediaQuery.sizeOf(context).shortestSide; final bool useMobileLayout = shortestSide < 600; if (!useMobileLayout) isFocused = true; final s = S.of(context); FlutterNativeSplash.remove(); return BlocProvider( create: (BuildContext context) => authCubit, child: BlocBuilder( builder: (BuildContext context, AuthState state) { return Scaffold( appBar: AppBar( centerTitle: true, toolbarHeight: isFocused ? 64 : MediaQuery.of(context).size.width - 64, title: GestureDetector( onTap: () { tapsCount++; if (tapsCount == 7) { tapsCount = 0; GoRouter.of(context).pushNamed(DevToolsScreen.name); } }, child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: isFocused ? 64 : MediaQuery.of(context).size.width - 64, fit: BoxFit.contain), ), ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Social auth buttons at the top (outlined) const Text('Авторизация через:', style: TextStyle(fontWeight: FontWeight.w600)), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _SocialButton( onTap: () => _onVkPressed(context), backgroundColor: const Color(0xFF2787F5), tooltip: 'VK ID', child: SvgPicture.asset('assets/icons/vk_logo.svg', height: 24, width: 24), ), _SocialButton( onTap: _onOkPressed, backgroundColor: const Color(0xFFF58220), tooltip: 'Одноклассники', child: SvgPicture.asset('assets/icons/ok_logo.svg', height: 24, width: 24), ), _SocialButton( onTap: _onMailRuPressed, backgroundColor: const Color(0xFF005FF9), tooltip: 'Mail.ru', child: SvgPicture.asset('assets/icons/mail_logo.svg', height: 24, width: 24), ), _SocialButton( onTap: _onYandexPressed, backgroundColor: const Color(0xFFFFCC00), tooltip: 'Яндекс ID', child: SvgPicture.asset('assets/icons/yandex_logo.svg', height: 24, width: 24), ), ], ), const SizedBox(height: 16), const CustomDivider(), const SizedBox(height: 8), const SizedBox(height: 16), Text(s.email), const SizedBox(height: 4), 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), 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), Center( child: GestureDetector( onTap: () => GoRouter.of(context).goNamed('forgetPasswordScreen', extra: false), child: Text( 'Забыли пароль?', style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline), ), ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Нет аккаунта? '), GestureDetector( onTap: () => GoRouter.of(context).goNamed('registrationScreen'), child: Text( 'Зарегистрироваться', style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline), ), ), ], ), const Spacer(), Center( child: RichText( textAlign: TextAlign.center, text: TextSpan( text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n', style: const TextStyle(fontSize: 12, color: Colors.black), children: [ const TextSpan(text: 'c '), 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')), ), const TextSpan(text: ' и '), ], TextSpan( text: 'Политикой конфиденциальности\nи защитой персональных данных', style: TextStyle( fontSize: 12, color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline, ), recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')), ), ], ), ), ), const SizedBox(height: 16), Row( children: [ SizedBox( height: 48, width: MediaQuery.of(context).size.width - 88, child: FloatingActionButton.extended( heroTag: 'authButton', highlightElevation: 0, elevation: 0, focusElevation: 0, 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); if (isAuthenticated) { if (context.mounted) { _reconnectWebSocket(context); } } }, label: state is AuthStateLoading ? const CupertinoActivityIndicator() : Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)), ), ), const SizedBox(width: 8), SizedBox( height: 48, width: 48, child: FloatingActionButton.extended( heroTag: 'scanQrCodeButton', highlightElevation: 0, elevation: 0, focusElevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400, onPressed: state is AuthStateLoading ? null : () async { await showDialog( context: context, builder: (BuildContext context) { return Dialog( child: Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text('Где взять QR-код?\n', style: TextStyle(fontSize: 18)), Text( '1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"', ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( borderRadius: BorderRadius.circular(8), onTap: () { AppRouter.navigatorKey.currentContext?.pop(); }, child: Ink( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: const Text( 'Отмена', style: TextStyle(color: Colors.black, fontWeight: FontWeight.w400), ), ), ), const SizedBox(width: 8), InkWell( borderRadius: BorderRadius.circular(8), onTap: () { AppRouter.navigatorKey.currentContext?.pop(); AppRouter.navigatorKey.currentContext?.pushNamed('scanQRCodeScreen'); }, child: Ink( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: Text( 'Перейти', style: TextStyle(color: appTheme.blueA400, fontWeight: FontWeight.w600), ), ), ), ], ), ], ), ), ); }, ); }, label: state is AuthStateLoading ? const CupertinoActivityIndicator() : const Icon(Icons.qr_code_scanner_rounded, color: Colors.white), ), ), ], ), ], ), ), ), ); }, ), ); } } class _SocialButton extends StatelessWidget { const _SocialButton({required this.onTap, required this.child, required this.backgroundColor, required this.tooltip}); final VoidCallback onTap; final Widget child; final Color backgroundColor; final String tooltip; @override Widget build(BuildContext context) { return Tooltip( message: tooltip, child: InkWell( borderRadius: BorderRadius.circular(20), onTap: onTap, child: Ink( height: 48, width: (MediaQuery.of(context).size.width - 16 * 2 - 12 * 3) / 4, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFFDBDBDB), width: 1), ), child: Center(child: child), ), ), ); } } void _onVkPressed(BuildContext context) async { print('[VK] VK button pressed'); try { final accessToken = await SocialAuthFacade.signInWithVk(context: context); print('[VK] Got access token: ${accessToken != null ? 'success' : 'null'}'); if (accessToken == null) { print('[VK] No access token received'); return; } final isAuthenticated = await sl().getTokenBySocial(provider: 'vk', accessToken: accessToken); print('[VK] Authentication result: $isAuthenticated'); if (isAuthenticated) { if (AppRouter.navigatorKey.currentContext != null) { // Reuse success flow // ignore: use_build_context_synchronously await _reconnectAfterAuth(); } } } catch (e) { print('[VK] Error during VK auth: $e'); } } void _onOkPressed() async { print('[OK] OK button pressed'); try { final accessToken = await SocialAuthFacade.signInWithOk(); print('[OK] Got access token: ${accessToken != null ? 'success' : 'null'}'); if (accessToken == null) { print('[OK] No access token received'); return; } final isAuthenticated = await sl().getTokenBySocial(provider: 'ok', accessToken: accessToken); print('[OK] Authentication result: $isAuthenticated'); if (isAuthenticated) { await _reconnectAfterAuth(); } } catch (e) { print('[OK] Error during OK auth: $e'); } } void _onMailRuPressed() async { print('[MailRu] MailRu button pressed'); try { final accessToken = await SocialAuthFacade.signInWithMailRu(); print('[MailRu] Got access token: ${accessToken != null ? 'success' : 'null'}'); if (accessToken == null) { print('[MailRu] No access token received'); return; } final isAuthenticated = await sl().getTokenBySocial(provider: 'mailru', accessToken: accessToken); print('[MailRu] Authentication result: $isAuthenticated'); if (isAuthenticated) { await _reconnectAfterAuth(); } } catch (e) { print('[MailRu] Error during MailRu auth: $e'); } } void _onYandexPressed() async { final accessToken = await SocialAuthFacade.signInWithYandex(); if (accessToken == null) return; final isAuthenticated = await sl().getTokenBySocial(provider: 'yandex', accessToken: accessToken); if (isAuthenticated) { await _reconnectAfterAuth(); } } Future _reconnectAfterAuth() async { final context = AppRouter.navigatorKey.currentContext; if (context == null) return; try { await sl().clear(); await sl().initialize(); await sl().getMyProfile(); await sl().getChatRoomsWithLoading(); await sl().connectToWebSocket(); await sl().getNotifications(); await sl().connectToWebSocket(); await sl().registerPushToken(); showSuccessToast('Успешный вход'); } catch (_) {} AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false}); }