diff --git a/integration_test/auth_screen/auth_login_test.dart b/integration_test/auth_screen/auth_login_test.dart new file mode 100644 index 00000000..711e0aa3 --- /dev/null +++ b/integration_test/auth_screen/auth_login_test.dart @@ -0,0 +1,38 @@ +import 'package:eatvio_app/app_keys.dart'; +import 'package:eatvio_app/core/app_initializer.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:eatvio_app/main.dart' as app; + +import '../common_cases/skip_welcome_screen.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() async { + await AppInitializer.initialize(buildType: AppBuildType.tests); + }); + + testWidgets('Signs in successfully with valid credentials', (WidgetTester tester) async { + await tester.pumpWidget(const app.MyApp()); + await tester.pumpAndSettle(const Duration(seconds: 3)); + await skipWelcomeScreen(tester); + final emailFieldFinder = find.byKey(AppKeys.emailTextFieldAuthScreen); + final passwordFieldFinder = find.byKey(AppKeys.passwordTextFieldAuthScreen); + final signInButtonFinder = find.byKey(AppKeys.signInButtonAuthScreen); + + expect(emailFieldFinder, findsOneWidget, reason: 'Email text field should be found.'); + expect(passwordFieldFinder, findsOneWidget, reason: 'Password text field should be found.'); + expect(signInButtonFinder, findsOneWidget, reason: 'Sign In button should be found.'); + + await tester.enterText(emailFieldFinder, 'reshetnik.n+user3@gmail.com'); + await tester.enterText(passwordFieldFinder, '1234567890'); + await tester.pumpAndSettle(); + + await tester.tap(signInButtonFinder); + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + expect(signInButtonFinder, findsNothing, reason: 'Sign-in button should not be visible after successful login and navigation.'); + }); +} diff --git a/integration_test/auth_screen/auth_logo_button_test.dart b/integration_test/auth_screen/auth_logo_button_test.dart new file mode 100644 index 00000000..c427f327 --- /dev/null +++ b/integration_test/auth_screen/auth_logo_button_test.dart @@ -0,0 +1,32 @@ +import 'package:eatvio_app/app_keys.dart'; +import 'package:eatvio_app/core/app_initializer.dart'; +import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:eatvio_app/main.dart' as app; + +import '../common_cases/skip_welcome_screen.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() async { + await AppInitializer.initialize(buildType: AppBuildType.tests); + }); + + testWidgets('tapping logo 7 times opens dev tools', (WidgetTester tester) async { + await tester.pumpWidget(const app.MyApp()); + await tester.pumpAndSettle(const Duration(seconds: 3)); + await skipWelcomeScreen(tester); + final logoButtonFinder = find.byKey(AppKeys.hiddenLogoButtonAuthScreen); + expect(logoButtonFinder, findsOneWidget, reason: 'The logo GestureDetector should be found in auth screen'); + + for (int i = 0; i < 7; i++) { + await tester.tap(logoButtonFinder); + await tester.pump(const Duration(milliseconds: 50)); + } + await tester.pumpAndSettle(); + + expect(find.byType(DevToolsScreen), findsOneWidget, reason: 'DevToolsScreen should be visible after tapping the logo 7 times.'); + }); +} diff --git a/integration_test/common_cases/skip_welcome_screen.dart b/integration_test/common_cases/skip_welcome_screen.dart new file mode 100644 index 00000000..20f8899b --- /dev/null +++ b/integration_test/common_cases/skip_welcome_screen.dart @@ -0,0 +1,8 @@ +import 'package:eatvio_app/app_keys.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Future skipWelcomeScreen(WidgetTester tester) async { + final welcomeScreenButtonFinder = find.byKey(AppKeys.welcomeScreenButton); + await tester.tap(welcomeScreenButtonFinder); + await tester.pumpAndSettle(); +} \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b6363034..fc70b0b0 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ -import UIKit import Flutter +import UIKit +import YandexLoginSDK @main @objc class AppDelegate: FlutterAppDelegate { @@ -10,4 +11,20 @@ import Flutter GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + @available(iOS 8.0, *) + func application( + _ application: UIApplication, continue userActivity: NSUserActivity, + restorationHandler: @escaping ([Any]?) -> Void + ) -> Bool { + YXLSdk.shared.processUserActivity(userActivity) + return true + } + + override public func application( + _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + return YXLSdk.shared.handleOpen( + url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String) + } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7e82c5b2..42ec3497 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -67,5 +67,24 @@ UIViewControllerBasedStatusBarAppearance + LSApplicationQueriesSchemes + + yandexauth + yandexauth2 + yandexauth4 + + YAClientId + 5399b03636104c6d920b87c1ad329254 + CFBundleURLTypes + + + CFBundleURLName + YandexLoginSDK + CFBundleURLSchemes + + yx5399b03636104c6d920b87c1ad329254 + + + diff --git a/lib/app_keys.dart b/lib/app_keys.dart new file mode 100644 index 00000000..3e732506 --- /dev/null +++ b/lib/app_keys.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class AppKeys { + + // Auth Screen + static const hiddenLogoButtonAuthScreen = ValueKey('hiddenLogoButtonAuthScreen'); + static const emailTextFieldAuthScreen = ValueKey('emailTextFieldAuthScreen'); + static const passwordTextFieldAuthScreen = ValueKey('passwordTextFieldAuthScreen'); + static const signInButtonAuthScreen = ValueKey('signInButtonAuthScreen'); + + // Welcome Screen + static const welcomeScreenButton = ValueKey('welcomeScreenButton'); +} diff --git a/lib/core/app_initializer.dart b/lib/core/app_initializer.dart index a3798a34..45e89fcf 100644 --- a/lib/core/app_initializer.dart +++ b/lib/core/app_initializer.dart @@ -1,4 +1,5 @@ import 'dart:io' as io; +import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/locator_service.dart' as di; import 'package:eatvio_app/firebase_options.dart'; @@ -12,10 +13,19 @@ import 'package:sentry_flutter/sentry_flutter.dart'; /// Класс для инициализации приложения class AppInitializer { + /// Инициализация всех сервисов приложения - static Future initialize() async { - await _initializeSentry(); + static Future initialize({AppBuildType buildType = AppBuildType.prod}) async { await _initializeEnvironment(); + switch (buildType) { + case AppBuildType.tests: + baseUrl = EnvConfig.devBaseUrl; + buildType = AppBuildType.tests; + break; + case AppBuildType.prod: + break; + } + await _initializeSentry(); await _initializeFirebase(); await _initializeDependencyInjection(); await _initializeDebugMode(); @@ -66,3 +76,5 @@ class AppInitializer { }, appRunner: appRunner); } } + +enum AppBuildType { prod, tests } diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index 6f49ea2a..ce6fee32 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; +import 'package:eatvio_app/app_keys.dart'; import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/firebase_api.dart'; @@ -46,7 +47,7 @@ class _AuthScreenState extends State { late StreamSubscription keyboardSubscription; bool isFocused = false; int tapsCount = 0; - + bool _isLoading = false; @override void initState() { super.initState(); @@ -92,6 +93,7 @@ class _AuthScreenState extends State { @override Widget build(BuildContext context) { var shortestSide = MediaQuery.sizeOf(context).shortestSide; + final screenSize = MediaQuery.of(context).size; final bool useMobileLayout = shortestSide < 600; if (!useMobileLayout) isFocused = true; final s = S.of(context); @@ -102,9 +104,10 @@ class _AuthScreenState extends State { builder: (BuildContext context, AuthState state) { return Scaffold( appBar: AppBar( + toolbarHeight: screenSize.height * 0.15, centerTitle: true, - toolbarHeight: isFocused ? 64 : MediaQuery.of(context).size.width - 64, title: GestureDetector( + key: AppKeys.hiddenLogoButtonAuthScreen, onTap: () { tapsCount++; if (tapsCount == 7) { @@ -112,7 +115,7 @@ class _AuthScreenState extends State { 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: screenSize.height * 0.20), ), ), body: SafeArea( @@ -122,8 +125,8 @@ class _AuthScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Social auth buttons at the top (outlined) - const Text('Авторизация через:', style: TextStyle(fontWeight: FontWeight.w600)), - const SizedBox(height: 8), + const Center(child: Text('Авторизация через')), + const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -164,6 +167,7 @@ class _AuthScreenState extends State { textInputAction: TextInputAction.next, keyboardType: TextInputType.emailAddress, textInputType: TextInputType.emailAddress, + key: AppKeys.emailTextFieldAuthScreen, hintText: 'Введите email', ), const SizedBox(height: 16), @@ -172,6 +176,7 @@ class _AuthScreenState extends State { CustomTextFormField( obscureText: hidePassword, controller: passwordController, + key: AppKeys.passwordTextFieldAuthScreen, hintText: 'Введите пароль', suffix: IconButton( onPressed: () { @@ -249,21 +254,28 @@ class _AuthScreenState extends State { height: 48, width: MediaQuery.of(context).size.width - 88, child: FloatingActionButton.extended( + key: AppKeys.signInButtonAuthScreen, 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 + backgroundColor: (state is AuthStateLoading || _isLoading) ? Colors.black.withAlpha(50) : appTheme.blueA400, + onPressed: (state is AuthStateLoading || _isLoading) ? null : () async { + setState(() { + _isLoading = true; + }); final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text); if (isAuthenticated) { if (context.mounted) { - _reconnectWebSocket(context); + await _reconnectWebSocket(context); } } + setState(() { + _isLoading = false; + }); }, label: state is AuthStateLoading ? const CupertinoActivityIndicator() diff --git a/lib/features/welcome/presentation/pages/welcome_screen.dart b/lib/features/welcome/presentation/pages/welcome_screen.dart index 31b4bf0f..d0b8f867 100644 --- a/lib/features/welcome/presentation/pages/welcome_screen.dart +++ b/lib/features/welcome/presentation/pages/welcome_screen.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/app_keys.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:flutter/material.dart'; @@ -79,6 +80,7 @@ class _WelcomeScreenState extends State { ), const SizedBox(height: 24), InkWell( + key: AppKeys.welcomeScreenButton, onTap: () async { final sharedPreferences = await SharedPreferences.getInstance(); await sharedPreferences.setBool('welcomeScreenWasShowed', true); diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 0ccdeac3..1055e664 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,9 +28,10 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = + (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName; diff --git a/pubspec.yaml b/pubspec.yaml index 98a4429f..0891053c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,6 +88,8 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 + integration_test: + sdk: flutter flutter: uses-material-design: true