From 89a6dd28bfa8b8eec8492b37e1a361e1f0a1d176 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 14 May 2026 14:46:24 +0700 Subject: [PATCH 1/7] refactor(fitness-start): remove onboarding tests flow --- lib/core/constants/app_strings.dart | 9 +- lib/core/router/router.dart | 25 -- lib/core/router/router_paths.dart | 13 - .../pages/fitness_start_quiz_page.dart | 9 +- .../fitness_start_test_attempt_page.dart | 332 ------------------ ...tness_start_test_attempt_page_builder.dart | 27 -- .../pages/fitness_start_tests_page.dart | 123 ------- .../fitness_start_tests_page_builder.dart | 21 -- 8 files changed, 5 insertions(+), 554 deletions(-) delete mode 100644 lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page.dart delete mode 100644 lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart delete mode 100644 lib/features/fitness_start/presentation/pages/fitness_start_tests_page.dart delete mode 100644 lib/features/fitness_start/presentation/pages/fitness_start_tests_page_builder.dart diff --git a/lib/core/constants/app_strings.dart b/lib/core/constants/app_strings.dart index b40cdf2b..d04b8d72 100644 --- a/lib/core/constants/app_strings.dart +++ b/lib/core/constants/app_strings.dart @@ -151,16 +151,13 @@ abstract final class AppStrings { static const fitnessStartHeightRange = '140–210 см'; static const fitnessStartReferencesLoadFailed = 'Не удалось загрузить данные'; static const fitnessStartRetryButton = 'Попробовать снова'; - static const fitnessStartCompletedTitle = 'Обнаружены данные'; + static const fitnessStartCompletedTitle = 'Обнаружены сохраненные данные'; static const fitnessStartCompletedMessage = - 'Хотите пройти тест снова или использовать ранее введенную информацию?'; - static const fitnessStartRestartAction = 'Пройти тест снова'; + 'Хотите пройти фитнес-старт заново или использовать ранее введенную информацию?'; + static const fitnessStartRestartAction = 'Пройти заново'; static const fitnessStartRegisterAction = 'Использовать введенную информацию'; static const fitnessStartValidationFailed = 'Проверьте введенные данные и попробуйте снова'; static const fitnessStartUnknown = 'Не удалось выполнить действие. Попробуйте снова'; - static const fitnessStartTestsTitle = 'Персональная программа'; - static const fitnessStartTestsDescription = - 'Всего несколько быстрых тестов помогут подобрать безопасные и эффективные упражнения для Вашего уровня подготовки'; // Tests catalog. static const testsCatalogTitle = 'Тесты'; diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index c0330b0f..5c55204c 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -16,8 +16,6 @@ import '../../features/auth/presentation/pages/verify_email_route_args.dart'; import '../../features/auth/presentation/pages/verify_reset_code_page_builder.dart'; import '../../features/debug/presentation/debug_screen.dart'; import '../../features/fitness_start/presentation/pages/fitness_start_quiz_page_builder.dart'; -import '../../features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart'; -import '../../features/fitness_start/presentation/pages/fitness_start_tests_page_builder.dart'; import '../../features/offline/presentation/cubit/network_cubit.dart'; import '../../features/offline/presentation/pages/offline_page.dart'; import '../../features/profile/presentation/pages/profile_page_builder.dart'; @@ -376,29 +374,6 @@ final router = GoRouter( path: AppRoutePaths.fitnessStartQuizPath, builder: (_, _) => const FitnessStartQuizPageBuilder(), ), - GoRoute( - path: AppRoutePaths.fitnessStartTestsPath, - redirect: (_, state) { - if (state.extra == AppRoutePaths.fitnessStartQuizPath) { - return null; - } - return AppRoutePaths.fitnessStartQuizPath; - }, - builder: (_, _) => const FitnessStartTestsPageBuilder(), - ), - GoRoute( - path: AppRoutePaths.fitnessStartTestAttemptPath, - redirect: (_, state) { - final testingId = int.tryParse(state.pathParameters['testingId'] ?? ''); - if (testingId == null || testingId <= 0) { - return AppRoutePaths.fitnessStartTestsPath; - } - return null; - }, - builder: (_, state) => FitnessStartTestAttemptPageBuilder( - testingId: int.parse(state.pathParameters['testingId']!), - ), - ), ], ); diff --git a/lib/core/router/router_paths.dart b/lib/core/router/router_paths.dart index 1cace498..94856fcc 100644 --- a/lib/core/router/router_paths.dart +++ b/lib/core/router/router_paths.dart @@ -51,19 +51,6 @@ abstract class AppRoutePaths { /// Route path for the Fitness Start quiz. static const fitnessStartQuizPath = '$fitnessStartPrefix/quiz'; - /// Route path for the next onboarding step that will show tests. - static const fitnessStartTestsPath = '$fitnessStartPrefix/tests'; - - /// Base route path for a concrete test attempt inside Fitness Start. - static const fitnessStartTestAttemptBasePath = '$fitnessStartTestsPath/attempt'; - - /// Route path pattern for a concrete test attempt inside Fitness Start. - static const fitnessStartTestAttemptPath = '$fitnessStartTestAttemptBasePath/:testingId'; - - /// Builds the concrete route path for a test attempt by [testingId]. - static String fitnessStartTestAttemptDetailsPath(int testingId) => - '$fitnessStartTestAttemptBasePath/$testingId'; - /// Route path for workouts overview. static const workoutsPath = '/workouts'; diff --git a/lib/features/fitness_start/presentation/pages/fitness_start_quiz_page.dart b/lib/features/fitness_start/presentation/pages/fitness_start_quiz_page.dart index 33f06070..64d51827 100644 --- a/lib/features/fitness_start/presentation/pages/fitness_start_quiz_page.dart +++ b/lib/features/fitness_start/presentation/pages/fitness_start_quiz_page.dart @@ -3,11 +3,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; import '../../../../core/constants/app_assets.dart'; import '../../../../core/constants/app_strings.dart'; -import '../../../../core/router/router_paths.dart'; import '../../../../uikit/buttons/button_state.dart'; import '../../../../uikit/buttons/main_button.dart'; import '../../../../uikit/buttons/option_button.dart'; @@ -155,17 +153,14 @@ class _FitnessStartQuizPageState extends State { _fitnessStartCubit.clearFailure(); } if (state.isCompleted) { - context.go( - AppRoutePaths.fitnessStartTestsPath, - extra: AppRoutePaths.fitnessStartQuizPath, - ); + _authSessionCubit.completeGuestFitnessStart(); } }, builder: (context, state) { return Scaffold( appBar: FitnessStartFlowAppBar( title: AppStrings.fitnessStartTitle, - progress: (state.currentStep + 1) / 4, + progress: (state.currentStep + 1) / 3, showBackButton: isGuestOnboarding, onBackPressed: isGuestOnboarding ? () => unawaited(_handleGuestBack(state)) : null, ), diff --git a/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page.dart b/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page.dart deleted file mode 100644 index e4ddb74a..00000000 --- a/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; - -import '../../../../core/constants/app_assets.dart'; -import '../../../../core/constants/app_strings.dart'; -import '../../../../uikit/buttons/button_state.dart'; -import '../../../../uikit/buttons/main_button.dart'; -import '../../../../uikit/buttons/option_button.dart'; -import '../../../../uikit/cards/app_card.dart'; -import '../../../../uikit/dialogs/app_feedback_dialog.dart'; -import '../../../../uikit/images/network_image_widget.dart'; -import '../../../../uikit/images/svg_picture_widget.dart'; -import '../../../../uikit/themes/colors/app_color_theme.dart'; -import '../../../../uikit/themes/text/app_text_theme.dart'; -import '../../../auth/presentation/cubits/auth_session_cubit.dart'; -import '../../../tests/attempt/presentation/cubits/test_attempt_cubit.dart'; -import '../widgets/fitness_start_flow_app_bar.dart'; - -/// Fitness Start page for a single guest test attempt. -class FitnessStartTestAttemptPage extends StatefulWidget { - /// Testing identifier used for start retry. - final int testingId; - - /// Creates an instance of [FitnessStartTestAttemptPage]. - const FitnessStartTestAttemptPage({ - required this.testingId, - super.key, - }); - - @override - State createState() => _FitnessStartTestAttemptPageState(); -} - -class _FitnessStartTestAttemptPageState extends State { - final _pulseFormKey = GlobalKey(); - final _pulseController = TextEditingController(); - final _resultLabels = const [ - AppStrings.testsAttemptResultVeryPoor, - AppStrings.testsAttemptResultPoor, - AppStrings.testsAttemptResultNormal, - AppStrings.testsAttemptResultGood, - ]; - - TestAttemptCubit get _cubit => context.read(); - - @override - void dispose() { - _pulseController.dispose(); - super.dispose(); - } - - String? _validatePulse(String? value) { - final trimmedValue = value?.trim() ?? ''; - if (trimmedValue.isEmpty) return AppStrings.testsAttemptPulseRequired; - - final pulse = int.tryParse(trimmedValue); - if (pulse == null) return AppStrings.testsAttemptPulseInvalid; - if (pulse < 30 || pulse > 220) return AppStrings.testsAttemptPulseRange; - - return null; - } - - Future _submitPulse() async { - FocusScope.of(context).unfocus(); - final form = _pulseFormKey.currentState; - if (form == null || !form.validate()) return; - - await _cubit.submitPulse(int.parse(_pulseController.text.trim())); - } - - @override - Widget build(BuildContext context) { - final colorTheme = AppColorTheme.of(context); - return BlocConsumer( - listenWhen: (previous, current) => - previous.failure != current.failure || !previous.isCompleted && current.isCompleted, - listener: (context, state) { - final failure = state.failure; - if (failure != null && state.testing != null) { - showAppFeedbackDialog( - context, - title: AppStrings.feedbackErrorTitle, - message: failure.message, - ); - _cubit.clearFailure(); - } - if (state.isCompleted) { - unawaited(context.read().completeGuestFitnessStart()); - } - }, - builder: (context, state) { - return Scaffold( - appBar: FitnessStartFlowAppBar( - title: AppStrings.testsAttemptTitle, - progress: 0.9, - showBackButton: true, - onBackPressed: context.pop, - ), - body: Stack( - children: [ - Positioned( - right: -85, - bottom: -110, - child: IgnorePointer( - child: ExcludeSemantics( - child: SvgPictureWidget.frame( - AppAssets.imageFigure, - color: colorTheme.secondary.withValues(alpha: 0.3), - ), - ), - ), - ), - if (state.testing == null) - _buildStartState(context, state) - else - _buildLoadedState(context, state), - ], - ), - ); - }, - ); - } - - Widget _buildLoadingState() { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 32), - child: Center( - child: SizedBox.square( - dimension: 24, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ), - ), - ); - } - - Widget _buildStartState(BuildContext context, TestAttemptState state) { - if (state.isStarting) return _buildLoadingState(); - - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - return Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppStrings.testsStartFailed, - textAlign: TextAlign.center, - style: textTheme.bodyMedium.copyWith(color: colorTheme.onSurface), - ), - const SizedBox(height: 24), - MainButton( - onPressed: () => _cubit.startTest(widget.testingId), - child: const Text(AppStrings.fitnessStartRetryButton), - ), - ], - ), - ), - ); - } - - Widget _buildLoadedState(BuildContext context, TestAttemptState state) { - if (state.isCompleted) return _buildLoadingState(); - if (state.isAwaitingPulse) return _buildPulseStepLayout(context, state); - if (state.currentExercise == null) return _buildLoadingState(); - - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - return SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(24, 12, 24, 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - AppStrings.testsAttemptDescription, - textAlign: TextAlign.center, - style: textTheme.bodyMedium.copyWith( - fontWeight: FontWeight.w500, - color: colorTheme.onSurface, - ), - ), - const SizedBox(height: 20), - _buildExerciseContent(context, state), - ], - ), - ); - } - - Widget _buildPulseStepLayout(BuildContext context, TestAttemptState state) { - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - return Padding( - padding: const EdgeInsets.fromLTRB(24, 12, 24, 64), - child: SizedBox.expand( - child: Form( - key: _pulseFormKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - AppStrings.testsAttemptPulseTitle, - textAlign: TextAlign.center, - style: textTheme.bodyMedium.copyWith( - fontWeight: FontWeight.w500, - color: colorTheme.onSurface, - ), - ), - const SizedBox(height: 20), - Expanded( - child: SingleChildScrollView( - child: _buildPulseContent(context, state), - ), - ), - const SizedBox(height: 24), - MainButton( - state: state.isCompleting ? ButtonState.loading : ButtonState.enabled, - onPressed: _submitPulse, - child: const Text(AppStrings.testsAttemptCompleteButton), - ), - ], - ), - ), - ), - ); - } - - Widget _buildExerciseContent(BuildContext context, TestAttemptState state) { - final exercise = state.currentExercise!; - final testing = state.testing!; - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - final buttonState = state.isSubmittingResult ? ButtonState.disabled : ButtonState.enabled; - return AppCard( - child: LayoutBuilder( - builder: (context, constraints) { - final itemWidth = (constraints.maxWidth - 8) / 2; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: NetworkImageWidget( - imageUrl: exercise.imageUrl, - height: constraints.maxWidth, - ), - ), - const SizedBox(height: 20), - Text( - testing.title, - textAlign: TextAlign.end, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: textTheme.bodyMedium.copyWith( - fontSize: 16, - height: 24 / 16, - fontWeight: FontWeight.w500, - color: colorTheme.onSurface, - ), - ), - const SizedBox(height: 12), - Text( - exercise.description, - textAlign: TextAlign.end, - maxLines: 4, - overflow: TextOverflow.ellipsis, - style: textTheme.body.copyWith(color: colorTheme.hint), - ), - const SizedBox(height: 24), - Wrap( - spacing: 8, - runSpacing: 8, - children: List.generate(4, (index) { - final value = index + 1; - final label = _resultLabels[index]; - return SizedBox( - width: itemWidth, - child: OptionButton( - state: buttonState, - onPressed: () => _cubit.submitResult(value), - child: Text(label), - ), - ); - }), - ), - ], - ); - }, - ), - ); - } - - Widget _buildPulseContent(BuildContext context, TestAttemptState state) { - final exercise = state.currentExercise; - if (exercise == null) { - return const SizedBox.shrink(); - } - - return LayoutBuilder( - builder: (context, constraints) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: NetworkImageWidget( - imageUrl: exercise.imageUrl, - height: constraints.maxWidth, - ), - ), - const SizedBox(height: 20), - TextFormField( - controller: _pulseController, - enabled: !state.isCompleting, - keyboardType: TextInputType.number, - validator: _validatePulse, - textInputAction: TextInputAction.done, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - decoration: const InputDecoration( - hintText: AppStrings.testsAttemptPulseHint, - ), - ), - ], - ); - }, - ); - } -} diff --git a/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart b/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart deleted file mode 100644 index 7224462f..00000000 --- a/lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../../../../core/di/di.dart'; -import '../../../tests/attempt/domain/repositories/test_attempt_repository.dart'; -import '../../../tests/attempt/presentation/cubits/test_attempt_cubit.dart'; -import 'fitness_start_test_attempt_page.dart'; - -/// Builder for the Fitness Start test attempt page. -class FitnessStartTestAttemptPageBuilder extends StatelessWidget { - /// Testing identifier to be started on page open. - final int testingId; - - /// Creates an instance of [FitnessStartTestAttemptPageBuilder]. - const FitnessStartTestAttemptPageBuilder({ - required this.testingId, - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (_) => TestAttemptCubit(di())..startTest(testingId), - child: FitnessStartTestAttemptPage(testingId: testingId), - ); - } -} diff --git a/lib/features/fitness_start/presentation/pages/fitness_start_tests_page.dart b/lib/features/fitness_start/presentation/pages/fitness_start_tests_page.dart deleted file mode 100644 index 24a9b9d5..00000000 --- a/lib/features/fitness_start/presentation/pages/fitness_start_tests_page.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; - -import '../../../../core/constants/app_assets.dart'; -import '../../../../core/constants/app_strings.dart'; -import '../../../../core/router/router_paths.dart'; -import '../../../../uikit/buttons/main_button.dart'; -import '../../../../uikit/images/svg_picture_widget.dart'; -import '../../../../uikit/themes/colors/app_color_theme.dart'; -import '../../../../uikit/themes/text/app_text_theme.dart'; -import '../../../auth/presentation/cubits/auth_session_cubit.dart'; -import '../../../tests/catalog/presentation/cubits/tests_catalog_cubit.dart'; -import '../../../tests/catalog/presentation/widgets/testing_catalog_carousel.dart'; -import '../widgets/fitness_start_flow_app_bar.dart'; - -/// Fitness Start screen that displays tests catalog carousel. -class FitnessStartTestsPage extends StatelessWidget { - /// Creates an instance of [FitnessStartTestsPage]. - const FitnessStartTestsPage({super.key}); - - @override - Widget build(BuildContext context) { - final authSessionCubit = context.read(); - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - return Scaffold( - appBar: FitnessStartFlowAppBar( - title: AppStrings.fitnessStartTestsTitle, - progress: 0.75, - showBackButton: true, - onBackPressed: () => unawaited(authSessionCubit.cancelGuestFlow()), - ), - body: Stack( - children: [ - Positioned( - right: -120, - bottom: -180, - child: IgnorePointer( - child: ExcludeSemantics( - child: SvgPictureWidget.frame( - AppAssets.imageFigure, - color: colorTheme.primary.withValues(alpha: 0.3), - ), - ), - ), - ), - SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(24, 12, 24, 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - AppStrings.fitnessStartTestsDescription, - textAlign: TextAlign.center, - style: textTheme.bodyMedium.copyWith( - fontWeight: FontWeight.w500, - color: colorTheme.onSurface, - ), - ), - const SizedBox(height: 20), - BlocBuilder( - builder: (context, state) { - return state.when( - initial: SizedBox.shrink, - inProgress: _buildLoadingState, - loaded: (items) { - if (items.isEmpty) return _buildRetryState(context); - return TestingCatalogCarousel( - items: items, - onTestingSelected: (item) => context.push( - AppRoutePaths.fitnessStartTestAttemptDetailsPath(item.id), - ), - ); - }, - failed: (_) => _buildRetryState(context), - ); - }, - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildLoadingState() { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 32), - child: Center( - child: SizedBox.square( - dimension: 24, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ), - ), - ); - } - - Widget _buildRetryState(BuildContext context) { - final textTheme = AppTextTheme.of(context); - final colorTheme = AppColorTheme.of(context); - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppStrings.testsLoadFailed, - textAlign: TextAlign.center, - style: textTheme.bodyMedium.copyWith(color: colorTheme.onSurface), - ), - const SizedBox(height: 24), - MainButton( - onPressed: context.read().loadTestings, - child: const Text(AppStrings.fitnessStartRetryButton), - ), - ], - ), - ); - } -} diff --git a/lib/features/fitness_start/presentation/pages/fitness_start_tests_page_builder.dart b/lib/features/fitness_start/presentation/pages/fitness_start_tests_page_builder.dart deleted file mode 100644 index 656c625c..00000000 --- a/lib/features/fitness_start/presentation/pages/fitness_start_tests_page_builder.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../../../../core/di/di.dart'; -import '../../../tests/catalog/domain/repositories/tests_catalog_repository.dart'; -import '../../../tests/catalog/presentation/cubits/tests_catalog_cubit.dart'; -import 'fitness_start_tests_page.dart'; - -/// Builder for the Fitness Start tests page. -class FitnessStartTestsPageBuilder extends StatelessWidget { - /// Creates an instance of [FitnessStartTestsPageBuilder]. - const FitnessStartTestsPageBuilder({super.key}); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (_) => TestsCatalogCubit(di())..loadTestings(), - child: const FitnessStartTestsPage(), - ); - } -} From d1c324b6ebb63704c5b0e19b7b87b7417d5eb901 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 14 May 2026 15:01:10 +0700 Subject: [PATCH 2/7] refactor(tests): remove guest test attempt flow --- lib/core/di/di.dart | 13 +- lib/core/network/api_paths.dart | 6 - .../guest_test_attempt_repository_impl.dart | 87 ------- ...dart => test_attempt_repository_impl.dart} | 6 +- .../repositories/test_attempt_repository.dart | 6 - .../pages/tests_attempt_page_builder.dart | 3 +- .../tests/data/remote/tests_api_client.dart | 19 -- ...est_test_attempt_repository_impl_test.dart | 229 ------------------ ...=> test_attempt_repository_impl_test.dart} | 16 +- 9 files changed, 15 insertions(+), 370 deletions(-) delete mode 100644 lib/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart rename lib/features/tests/attempt/data/repositories/{authenticated_test_attempt_repository_impl.dart => test_attempt_repository_impl.dart} (93%) delete mode 100644 test/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl_test.dart rename test/features/tests/attempt/data/repositories/{authenticated_test_attempt_repository_impl_test.dart => test_attempt_repository_impl_test.dart} (94%) diff --git a/lib/core/di/di.dart b/lib/core/di/di.dart index 884ad471..115de91c 100644 --- a/lib/core/di/di.dart +++ b/lib/core/di/di.dart @@ -34,8 +34,7 @@ import '../../features/subscriptions/data/remote/subscription_payment_api_client import '../../features/subscriptions/data/remote/subscriptions_api_client.dart'; import '../../features/subscriptions/data/repositories/subscriptions_repository_impl.dart'; import '../../features/subscriptions/domain/repositories/subscriptions_repository.dart'; -import '../../features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart'; -import '../../features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart'; +import '../../features/tests/attempt/data/repositories/test_attempt_repository_impl.dart'; import '../../features/tests/attempt/domain/repositories/test_attempt_repository.dart'; import '../../features/tests/catalog/data/repositories/tests_catalog_repository_impl.dart'; import '../../features/tests/catalog/domain/repositories/tests_catalog_repository.dart'; @@ -204,14 +203,8 @@ Future setupDI() async { di(), ), ); - di.registerLazySingleton( - () => GuestTestAttemptRepositoryImpl( - di(), - di(), - ), - ); - di.registerLazySingleton( - () => AuthenticatedTestAttemptRepositoryImpl( + di.registerLazySingleton( + () => TestAttemptRepositoryImpl( di(), di(), ), diff --git a/lib/core/network/api_paths.dart b/lib/core/network/api_paths.dart index c38e9903..856f0097 100644 --- a/lib/core/network/api_paths.dart +++ b/lib/core/network/api_paths.dart @@ -95,12 +95,6 @@ abstract class ApiPaths { /// The endpoint prefix for authenticated test attempts. static const String testAttempts = '${apiPrefix}test-attempts'; - /// The endpoint prefix for guest tests. - static const String guestTests = '${apiPrefix}guest/tests'; - - /// The endpoint prefix for guest test attempts. - static const String guestTestAttempts = '${apiPrefix}guest/test-attempts'; - /// The endpoint for the current user workouts overview. static const String workouts = '${apiPrefix}workouts'; diff --git a/lib/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart b/lib/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart deleted file mode 100644 index e654ddac..00000000 --- a/lib/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:dio/dio.dart'; - -import '../../../../../core/failures/feature/tests/tests_failure.dart'; -import '../../../../../core/network/mappers/dio_exception_mapper.dart'; -import '../../../../../core/result/result.dart'; -import '../../../../../core/utils/logger/app_logger.dart'; -import '../../../catalog/data/mappers/tests_failure_mapper.dart'; -import '../../../data/remote/tests_api_client.dart'; -import '../../domain/entities/test_attempt_result.dart'; -import '../../domain/entities/test_attempt_start.dart'; -import '../../domain/repositories/test_attempt_repository.dart'; -import '../dto/complete_test_request_dto.dart'; -import '../dto/save_test_result_request_dto.dart'; -import '../mappers/test_attempt_mapper.dart'; -import 'test_attempt_result_payload_validator.dart'; - -/// Guest implementation of [GuestTestAttemptRepository]. -final class GuestTestAttemptRepositoryImpl implements GuestTestAttemptRepository { - /// Logger for tracking guest test attempt operations. - final AppLogger _logger; - - /// API client for tests catalog and guest attempts. - final TestsApiClient _apiClient; - - /// Creates an instance of [GuestTestAttemptRepositoryImpl]. - GuestTestAttemptRepositoryImpl(this._logger, this._apiClient); - - @override - Future> startTest(int testingId) async { - try { - final response = await _apiClient.startGuestTest(testingId); - return Result.success(response.data.toEntity()); - } on DioException catch (e) { - final networkFailure = e.toNetworkFailure(); - return Result.failure(networkFailure.toTestsFailure()); - } catch (e, s) { - _logger.e('StartGuestTest failed with unexpected error', e, s); - return Result.failure(UnknownTestsFailure(parentException: e, stackTrace: s)); - } - } - - @override - Future> saveResult({ - required String attemptId, - required int testingExerciseId, - required int resultValue, - }) async { - try { - final request = SaveTestResultRequestDto( - testingExerciseId: testingExerciseId, - resultValue: resultValue, - ); - final response = await _apiClient.saveGuestTestResult(attemptId, request); - final payload = response.data; - if (!isValidTestAttemptResultPayload(payload)) { - final exception = StateError('Malformed guest test result payload.'); - _logger.e('SaveGuestTestResult returned malformed payload', exception); - return Result.failure(UnknownTestsFailure(parentException: exception)); - } - return Result.success(payload.toEntity()); - } on DioException catch (e) { - final networkFailure = e.toNetworkFailure(); - return Result.failure(networkFailure.toTestsFailure()); - } catch (e, s) { - _logger.e('SaveGuestTestResult failed with unexpected error', e, s); - return Result.failure(UnknownTestsFailure(parentException: e, stackTrace: s)); - } - } - - @override - Future> completeTest({ - required String attemptId, - required int pulse, - }) async { - try { - final request = CompleteTestRequestDto(pulse: pulse); - await _apiClient.completeGuestTest(attemptId, request); - return const Result.success(null); - } on DioException catch (e) { - final networkFailure = e.toNetworkFailure(); - return Result.failure(networkFailure.toTestsFailure()); - } catch (e, s) { - _logger.e('CompleteGuestTest failed with unexpected error', e, s); - return Result.failure(UnknownTestsFailure(parentException: e, stackTrace: s)); - } - } -} diff --git a/lib/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart b/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart similarity index 93% rename from lib/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart rename to lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart index 3f3de555..66a49512 100644 --- a/lib/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart +++ b/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart @@ -15,15 +15,15 @@ import '../mappers/test_attempt_mapper.dart'; import 'test_attempt_result_payload_validator.dart'; /// Authenticated implementation of [AuthenticatedTestAttemptRepository]. -final class AuthenticatedTestAttemptRepositoryImpl implements AuthenticatedTestAttemptRepository { +final class TestAttemptRepositoryImpl implements TestAttemptRepository { /// Logger for tracking authenticated test attempt operations. final AppLogger _logger; /// API client for tests catalog and attempts. final TestsApiClient _apiClient; - /// Creates an instance of [AuthenticatedTestAttemptRepositoryImpl]. - AuthenticatedTestAttemptRepositoryImpl(this._logger, this._apiClient); + /// Creates an instance of [TestAttemptRepositoryImpl]. + TestAttemptRepositoryImpl(this._logger, this._apiClient); @override Future> startTest(int testingId) async { diff --git a/lib/features/tests/attempt/domain/repositories/test_attempt_repository.dart b/lib/features/tests/attempt/domain/repositories/test_attempt_repository.dart index 8a888eaf..a453e008 100644 --- a/lib/features/tests/attempt/domain/repositories/test_attempt_repository.dart +++ b/lib/features/tests/attempt/domain/repositories/test_attempt_repository.dart @@ -21,9 +21,3 @@ abstract interface class TestAttemptRepository { required int pulse, }); } - -/// Repository contract for guest test attempts. -abstract interface class GuestTestAttemptRepository implements TestAttemptRepository {} - -/// Repository contract for authenticated test attempts. -abstract interface class AuthenticatedTestAttemptRepository implements TestAttemptRepository {} diff --git a/lib/features/tests/attempt/presentation/pages/tests_attempt_page_builder.dart b/lib/features/tests/attempt/presentation/pages/tests_attempt_page_builder.dart index 4934f61d..efa95aa8 100644 --- a/lib/features/tests/attempt/presentation/pages/tests_attempt_page_builder.dart +++ b/lib/features/tests/attempt/presentation/pages/tests_attempt_page_builder.dart @@ -20,8 +20,7 @@ class TestsAttemptPageBuilder extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => - TestAttemptCubit(di())..startTest(testingId), + create: (_) => TestAttemptCubit(di())..startTest(testingId), child: TestsAttemptPage(testingId: testingId), ); } diff --git a/lib/features/tests/data/remote/tests_api_client.dart b/lib/features/tests/data/remote/tests_api_client.dart index 62d2a3c3..37b8ae1c 100644 --- a/lib/features/tests/data/remote/tests_api_client.dart +++ b/lib/features/tests/data/remote/tests_api_client.dart @@ -5,7 +5,6 @@ import '../../../../core/network/api_paths.dart'; import '../../attempt/data/dto/complete_test_request_dto.dart'; import '../../attempt/data/dto/save_test_result_request_dto.dart'; import '../../attempt/data/dto/save_test_result_response_dto.dart'; -import '../../attempt/data/dto/start_guest_test_response_dto.dart'; import '../../attempt/data/dto/start_test_response_dto.dart'; import '../../catalog/data/dto/testings_response_dto.dart'; @@ -21,21 +20,10 @@ abstract class TestsApiClient { @GET(ApiPaths.testings) Future getTestings(); - /// Starts a guest test attempt and returns the first exercise. - @POST('${ApiPaths.guestTests}/{testing}/start') - Future startGuestTest(@Path('testing') int testingId); - /// Starts an authenticated test attempt and returns the first exercise. @POST('${ApiPaths.tests}/{testing}/start') Future startTest(@Path('testing') int testingId); - /// Stores the result for the current guest test exercise. - @POST('${ApiPaths.guestTestAttempts}/{attempt}/result') - Future saveGuestTestResult( - @Path('attempt') String attemptId, - @Body() SaveTestResultRequestDto request, - ); - /// Stores the result for the current authenticated test exercise. @POST('${ApiPaths.testAttempts}/{attempt}/result') Future saveTestResult( @@ -43,13 +31,6 @@ abstract class TestsApiClient { @Body() SaveTestResultRequestDto request, ); - /// Completes a guest test attempt with pulse value. - @POST('${ApiPaths.guestTestAttempts}/{attempt}/complete') - Future completeGuestTest( - @Path('attempt') String attemptId, - @Body() CompleteTestRequestDto request, - ); - /// Completes an authenticated test attempt with pulse value. @POST('${ApiPaths.testAttempts}/{attempt}/complete') Future completeTest( diff --git a/test/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl_test.dart b/test/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl_test.dart deleted file mode 100644 index d7a073fe..00000000 --- a/test/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl_test.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:moveup_flutter/core/failures/feature/tests/tests_failure.dart'; -import 'package:moveup_flutter/core/utils/logger/app_logger.dart'; -import 'package:moveup_flutter/features/tests/attempt/data/dto/complete_test_request_dto.dart'; -import 'package:moveup_flutter/features/tests/attempt/data/dto/save_test_result_request_dto.dart'; -import 'package:moveup_flutter/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart'; -import 'package:moveup_flutter/features/tests/attempt/domain/repositories/test_attempt_repository.dart'; -import 'package:moveup_flutter/features/tests/data/remote/tests_api_client.dart'; - -import '../../../catalog/support/testings_dto_fixtures.dart'; -import '../../support/test_attempt_dto_fixtures.dart'; -import 'guest_test_attempt_repository_impl_test.mocks.dart'; - -@GenerateNiceMocks([ - MockSpec(), - MockSpec(), -]) -void main() { - late MockAppLogger logger; - late MockTestsApiClient apiClient; - late TestAttemptRepository repository; - - setUp(() { - logger = MockAppLogger(); - apiClient = MockTestsApiClient(); - repository = GuestTestAttemptRepositoryImpl(logger, apiClient); - }); - - group('GuestTestAttemptRepositoryImpl', () { - group('GuestTestAttemptRepositoryImpl.startTest', () { - test('returns success(start) when api succeeds', () async { - final responseDto = createStartGuestTestResponseDto(); - final expectedStart = createTestAttemptStart(); - when(apiClient.startGuestTest(8)).thenAnswer((_) async => responseDto); - - final result = await repository.startTest(8); - - expect(result.isSuccess, isTrue); - expect(result.success, expectedStart); - - verify(apiClient.startGuestTest(8)).called(1); - verifyNoMoreInteractions(apiClient); - }); - - test('returns TestsRequestFailure when api returns server error', () async { - final exception = createTestsDioBadResponseException( - path: '/guest/tests/8/start', - statusCode: 500, - code: 'server_error', - ); - when(apiClient.startGuestTest(8)).thenThrow(exception); - - final result = await repository.startTest(8); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, exception); - - verify(apiClient.startGuestTest(8)).called(1); - verifyNoMoreInteractions(apiClient); - }); - - test('returns UnknownTestsFailure when unexpected exception occurs', () async { - final exception = Exception('unexpected_error'); - when(apiClient.startGuestTest(8)).thenThrow(exception); - - final result = await repository.startTest(8); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, exception); - - verify(apiClient.startGuestTest(8)).called(1); - verifyNoMoreInteractions(apiClient); - }); - }); - - group('GuestTestAttemptRepositoryImpl.saveResult', () { - test('returns success(result) when api succeeds with next exercise', () async { - final responseDto = createSaveGuestTestResultResponseDto( - nextExercise: createTestingExerciseDto(id: 17, orderNumber: 2), - allExercisesCompleted: false, - ); - final expectedResult = createTestAttemptNextExerciseResult(); - when(apiClient.saveGuestTestResult(any, any)).thenAnswer((_) async => responseDto); - - final result = await repository.saveResult( - attemptId: 'guest_attempt_1', - testingExerciseId: 16, - resultValue: 2, - ); - - expect(result.isSuccess, isTrue); - expect(result.success, expectedResult); - - final captured = verify( - apiClient.saveGuestTestResult(captureAny, captureAny), - ).captured; - expect(captured.first, 'guest_attempt_1'); - expect((captured.last as SaveTestResultRequestDto).toJson(), { - 'testing_exercise_id': 16, - 'result_value': 2, - }); - verifyNoMoreInteractions(apiClient); - }); - - test('returns success(result) when all exercises are completed', () async { - final responseDto = createSaveGuestTestResultResponseDto(allExercisesCompleted: true); - final expectedResult = createTestAttemptAwaitingPulseResult(); - when(apiClient.saveGuestTestResult(any, any)).thenAnswer((_) async => responseDto); - - final result = await repository.saveResult( - attemptId: 'guest_attempt_1', - testingExerciseId: 16, - resultValue: 4, - ); - - expect(result.isSuccess, isTrue); - expect(result.success, expectedResult); - - verify(apiClient.saveGuestTestResult(any, any)).called(1); - verifyNoMoreInteractions(apiClient); - }); - - test('returns UnknownTestsFailure when api returns malformed payload', () async { - final responseDto = createSaveGuestTestResultResponseDto(); - when(apiClient.saveGuestTestResult(any, any)).thenAnswer((_) async => responseDto); - - final result = await repository.saveResult( - attemptId: 'guest_attempt_1', - testingExerciseId: 16, - resultValue: 4, - ); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, isA()); - - verify(apiClient.saveGuestTestResult(any, any)).called(1); - verifyNoMoreInteractions(apiClient); - }); - - test('returns TestsRequestFailure when api throws DioException', () async { - final exception = createTestsDioBadResponseException( - path: '/guest/test-attempts/guest_attempt_1/result', - statusCode: 500, - code: 'server_error', - ); - when(apiClient.saveGuestTestResult(any, any)).thenThrow(exception); - - final result = await repository.saveResult( - attemptId: 'guest_attempt_1', - testingExerciseId: 16, - resultValue: 2, - ); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, exception); - - verify(apiClient.saveGuestTestResult(any, any)).called(1); - verifyNoMoreInteractions(apiClient); - }); - }); - - group('GuestTestAttemptRepositoryImpl.completeTest', () { - test('returns success(void) when api succeeds', () async { - when(apiClient.completeGuestTest(any, any)).thenAnswer((_) async {}); - - final result = await repository.completeTest( - attemptId: 'guest_attempt_1', - pulse: 151, - ); - - expect(result.isSuccess, isTrue); - - final captured = verify(apiClient.completeGuestTest(captureAny, captureAny)).captured; - expect(captured.first, 'guest_attempt_1'); - expect((captured.last as CompleteTestRequestDto).toJson(), { - 'pulse': 151, - }); - verifyNoMoreInteractions(apiClient); - }); - - test('returns TestsValidationFailure when api returns 422', () async { - final exception = createTestsDioBadResponseException( - path: '/guest/test-attempts/guest_attempt_1/complete', - statusCode: 422, - code: 'validation_failed', - errors: const { - 'pulse': ['Пульс должен быть от 30 до 220'], - }, - ); - when(apiClient.completeGuestTest(any, any)).thenThrow(exception); - - final result = await repository.completeTest( - attemptId: 'guest_attempt_1', - pulse: 151, - ); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, exception); - - verify(apiClient.completeGuestTest(any, any)).called(1); - verifyNoMoreInteractions(apiClient); - }); - - test('returns UnknownTestsFailure when unexpected exception occurs', () async { - final exception = Exception('unexpected_error'); - when(apiClient.completeGuestTest(any, any)).thenThrow(exception); - - final result = await repository.completeTest( - attemptId: 'guest_attempt_1', - pulse: 151, - ); - - expect(result.isFailure, isTrue); - expect(result.failure, isA()); - expect(result.failure!.parentException, exception); - - verify(apiClient.completeGuestTest(any, any)).called(1); - verifyNoMoreInteractions(apiClient); - }); - }); - }); -} diff --git a/test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart b/test/features/tests/attempt/data/repositories/test_attempt_repository_impl_test.dart similarity index 94% rename from test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart rename to test/features/tests/attempt/data/repositories/test_attempt_repository_impl_test.dart index a46e1220..d9ffa6d0 100644 --- a/test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart +++ b/test/features/tests/attempt/data/repositories/test_attempt_repository_impl_test.dart @@ -5,13 +5,13 @@ import 'package:moveup_flutter/core/failures/feature/tests/tests_failure.dart'; import 'package:moveup_flutter/core/utils/logger/app_logger.dart'; import 'package:moveup_flutter/features/tests/attempt/data/dto/complete_test_request_dto.dart'; import 'package:moveup_flutter/features/tests/attempt/data/dto/save_test_result_request_dto.dart'; -import 'package:moveup_flutter/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart'; +import 'package:moveup_flutter/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart'; import 'package:moveup_flutter/features/tests/attempt/domain/repositories/test_attempt_repository.dart'; import 'package:moveup_flutter/features/tests/data/remote/tests_api_client.dart'; import '../../../catalog/support/testings_dto_fixtures.dart'; import '../../support/test_attempt_dto_fixtures.dart'; -import 'authenticated_test_attempt_repository_impl_test.mocks.dart'; +import 'test_attempt_repository_impl_test.mocks.dart'; @GenerateNiceMocks([ MockSpec(), @@ -20,16 +20,16 @@ import 'authenticated_test_attempt_repository_impl_test.mocks.dart'; void main() { late MockAppLogger logger; late MockTestsApiClient apiClient; - late AuthenticatedTestAttemptRepository repository; + late TestAttemptRepository repository; setUp(() { logger = MockAppLogger(); apiClient = MockTestsApiClient(); - repository = AuthenticatedTestAttemptRepositoryImpl(logger, apiClient); + repository = TestAttemptRepositoryImpl(logger, apiClient); }); - group('AuthenticatedTestAttemptRepositoryImpl', () { - group('AuthenticatedTestAttemptRepositoryImpl.startTest', () { + group('TestAttemptRepositoryImpl', () { + group('TestAttemptRepositoryImpl.startTest', () { test('returns success(start) when api succeeds', () async { final responseDto = createStartTestResponseDto(); when(apiClient.startTest(8)).thenAnswer((_) async => responseDto); @@ -79,7 +79,7 @@ void main() { }); }); - group('AuthenticatedTestAttemptRepositoryImpl.saveResult', () { + group('TestAttemptRepositoryImpl.saveResult', () { test('returns success(result) when api succeeds with next exercise', () async { final responseDto = createSaveGuestTestResultResponseDto( nextExercise: createTestingExerciseDto(id: 17, orderNumber: 2), @@ -184,7 +184,7 @@ void main() { }); }); - group('AuthenticatedTestAttemptRepositoryImpl.completeTest', () { + group('TestAttemptRepositoryImpl.completeTest', () { test('returns success(void) when api succeeds', () async { when(apiClient.completeTest(any, any)).thenAnswer((_) async {}); From cd20dc13d96967ea9e80b5c98bddac1f93f5bfe1 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 14 May 2026 16:28:39 +0700 Subject: [PATCH 3/7] refactor: remove guest session storage and cookie management --- lib/core/di/di.dart | 23 ----------------- lib/core/network/dio_setup.dart | 5 ---- .../cookie_jar_guest_session_storage.dart | 17 ------------- .../guest_session_storage.dart | 5 ---- .../cubits/auth_session_cubit.dart | 21 +--------------- pubspec.lock | 24 ------------------ pubspec.yaml | 2 -- .../cubits/auth_session_cubit_test.dart | 25 ++----------------- 8 files changed, 3 insertions(+), 119 deletions(-) delete mode 100644 lib/core/services/guest_session_storage/cookie_jar_guest_session_storage.dart delete mode 100644 lib/core/services/guest_session_storage/guest_session_storage.dart diff --git a/lib/core/di/di.dart b/lib/core/di/di.dart index 115de91c..67ec86cc 100644 --- a/lib/core/di/di.dart +++ b/lib/core/di/di.dart @@ -1,13 +1,9 @@ -import 'dart:io'; - import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:logger/logger.dart'; -import 'package:path_provider/path_provider.dart'; import '../../features/auth/data/remote/auth_api_client.dart'; import '../../features/auth/data/repositories/auth_repository_impl.dart'; @@ -46,12 +42,9 @@ import '../../features/workouts/execution/data/repositories/workout_execution_re import '../../features/workouts/execution/domain/repositories/workout_execution_repository.dart'; import '../../features/workouts/overview/data/repositories/workouts_overview_repository_impl.dart'; import '../../features/workouts/overview/domain/repositories/workouts_overview_repository.dart'; -import '../network/api_paths.dart'; import '../network/dio_setup.dart'; import '../services/fitness_start_progress_storage/fitness_start_progress_storage.dart'; import '../services/fitness_start_progress_storage/hive_fitness_start_progress_storage.dart'; -import '../services/guest_session_storage/cookie_jar_guest_session_storage.dart'; -import '../services/guest_session_storage/guest_session_storage.dart'; import '../services/network/network_service.dart'; import '../services/network/network_service_impl.dart'; import '../services/token_storage/secure_token_storage.dart'; @@ -70,13 +63,6 @@ Future setupDI() async { final fitnessStartProgressBox = await Hive.openBox( HiveFitnessStartProgressStorage.boxName, ); - final supportDirectory = await getApplicationSupportDirectory(); - final cookiesDirectory = Directory( - '${supportDirectory.path}/guest_cookies', - ); - final cookieJar = PersistCookieJar( - storage: FileStorage(cookiesDirectory.path), - ); // Logger di.registerLazySingleton(() => createLogger()); @@ -104,20 +90,12 @@ Future setupDI() async { () => HiveFitnessStartProgressStorage(fitnessStartProgressBox), dispose: (_) => fitnessStartProgressBox.close(), ); - di.registerLazySingleton(() => cookieJar); - di.registerLazySingleton( - () => CookieJarGuestSessionStorage( - di(), - Uri.parse(ApiPaths.baseUrl), - ), - ); // Authentication di.registerLazySingleton( () => createDioClient( logger: di(), tokenStorage: di(), - cookieJar: di(), ), ); di.registerLazySingleton(() => AuthApiClient(di())); @@ -189,7 +167,6 @@ Future setupDI() async { di(), di(), di(), - di(), di(), ), dispose: (cubit) => cubit.close(), diff --git a/lib/core/network/dio_setup.dart b/lib/core/network/dio_setup.dart index 3a4bcf53..f7a24dc0 100644 --- a/lib/core/network/dio_setup.dart +++ b/lib/core/network/dio_setup.dart @@ -1,6 +1,4 @@ -import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; -import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:flutter/foundation.dart'; import '../services/token_storage/token_storage.dart'; @@ -13,7 +11,6 @@ import 'interceptors/logging_interceptor.dart'; Dio createDioClient({ required AppLogger logger, required TokenStorage tokenStorage, - required CookieJar cookieJar, }) { /// Main Dio instance for all API calls. final dio = Dio(_createDioBaseOptions()); @@ -21,8 +18,6 @@ Dio createDioClient({ /// Separate Dio instance for token refresh only to avoid interceptor loops. final refreshDio = Dio(_createDioBaseOptions()); - dio.interceptors.add(CookieManager(cookieJar)); - dio.interceptors.add( AuthInterceptor( tokenStorage, diff --git a/lib/core/services/guest_session_storage/cookie_jar_guest_session_storage.dart b/lib/core/services/guest_session_storage/cookie_jar_guest_session_storage.dart deleted file mode 100644 index f5ab414b..00000000 --- a/lib/core/services/guest_session_storage/cookie_jar_guest_session_storage.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:cookie_jar/cookie_jar.dart'; - -import 'guest_session_storage.dart'; - -/// CookieJar-backed implementation of [GuestSessionStorage]. -final class CookieJarGuestSessionStorage implements GuestSessionStorage { - final CookieJar _cookieJar; - final Uri _baseUri; - - /// Creates an instance of [CookieJarGuestSessionStorage]. - CookieJarGuestSessionStorage(this._cookieJar, this._baseUri); - - @override - Future clear() async { - await _cookieJar.delete(_baseUri, true); - } -} diff --git a/lib/core/services/guest_session_storage/guest_session_storage.dart b/lib/core/services/guest_session_storage/guest_session_storage.dart deleted file mode 100644 index 946004f2..00000000 --- a/lib/core/services/guest_session_storage/guest_session_storage.dart +++ /dev/null @@ -1,5 +0,0 @@ -/// Abstraction for clearing persisted guest backend session data. -abstract interface class GuestSessionStorage { - /// Clears any guest session data persisted locally. - Future clear(); -} diff --git a/lib/features/auth/presentation/cubits/auth_session_cubit.dart b/lib/features/auth/presentation/cubits/auth_session_cubit.dart index 5998aa71..6abd5230 100644 --- a/lib/features/auth/presentation/cubits/auth_session_cubit.dart +++ b/lib/features/auth/presentation/cubits/auth_session_cubit.dart @@ -6,7 +6,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../../core/failures/feature/auth/auth_failure.dart'; import '../../../../core/result/result.dart'; import '../../../../core/services/fitness_start_progress_storage/fitness_start_progress_storage.dart'; -import '../../../../core/services/guest_session_storage/guest_session_storage.dart'; import '../../../../core/services/token_storage/token_storage.dart'; import '../../../../core/utils/logger/app_logger.dart'; import '../../domain/entities/user.dart'; @@ -26,9 +25,6 @@ final class AuthSessionCubit extends Cubit { /// Storage for persisted guest Fitness Start progress. final FitnessStartProgressStorage _fitnessStartProgressStorage; - /// Storage for persisted guest backend session cookies. - final GuestSessionStorage _guestSessionStorage; - /// Logger for non-fatal session cleanup errors. final AppLogger _logger; @@ -37,7 +33,6 @@ final class AuthSessionCubit extends Cubit { this._repository, this._tokenStorage, this._fitnessStartProgressStorage, - this._guestSessionStorage, this._logger, ) : super(const AuthSessionState.initial()); @@ -127,22 +122,8 @@ final class AuthSessionCubit extends Cubit { } } - Future _clearGuestSessionSafely() async { - try { - await _guestSessionStorage.clear(); - return true; - } catch (e, s) { - _logger.e('Failed to clear guest session cookies.', e, s); - return false; - } - } - Future _clearGuestDataSafely() async { - final results = await Future.wait([ - _clearGuestProgressSafely(), - _clearGuestSessionSafely(), - ]); - return results.every((isSuccess) => isSuccess); + return _clearGuestProgressSafely(); } Future _clearGuestDataAfterAuthSuccess() async { diff --git a/pubspec.lock b/pubspec.lock index a6d26b23..fc5f1746 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,14 +225,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" - cookie_jar: - dependency: "direct main" - description: - name: cookie_jar - sha256: "963da02c1ef64cb5ac20de948c9e5940aa351f1e34a12b1d327c83d85b7e8fff" - url: "https://pub.dev" - source: hosted - version: "4.0.9" coverage: dependency: transitive description: @@ -289,14 +281,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.9.1" - dio_cookie_manager: - dependency: "direct main" - description: - name: dio_cookie_manager - sha256: "0db1a7b997a0455e488ac35744c68eed3f2a4280d3ab531835a65641b0a08744" - url: "https://pub.dev" - source: hosted - version: "3.4.0" dio_web_adapter: dependency: transitive description: @@ -1245,14 +1229,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 - url: "https://pub.dev" - source: hosted - version: "2.3.1" uri: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 06419637..c6f3e28e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,9 +10,7 @@ dependencies: cached_network_image: ^3.4.1 carousel_slider: ^5.1.1 connectivity_plus: ^7.0.0 - cookie_jar: ^4.0.8 dio: ^5.9.1 - dio_cookie_manager: ^3.3.0 envied: ^1.3.3 equatable: ^2.0.8 flutter: diff --git a/test/features/auth/presentation/cubits/auth_session_cubit_test.dart b/test/features/auth/presentation/cubits/auth_session_cubit_test.dart index 41d755c2..5158908d 100644 --- a/test/features/auth/presentation/cubits/auth_session_cubit_test.dart +++ b/test/features/auth/presentation/cubits/auth_session_cubit_test.dart @@ -5,7 +5,6 @@ import 'package:mockito/mockito.dart'; import 'package:moveup_flutter/core/failures/feature/auth/auth_failure.dart'; import 'package:moveup_flutter/core/result/result.dart'; import 'package:moveup_flutter/core/services/fitness_start_progress_storage/fitness_start_progress_storage.dart'; -import 'package:moveup_flutter/core/services/guest_session_storage/guest_session_storage.dart'; import 'package:moveup_flutter/core/services/token_storage/token_storage.dart'; import 'package:moveup_flutter/core/utils/logger/app_logger.dart'; import 'package:moveup_flutter/features/auth/domain/entities/user.dart'; @@ -18,14 +17,12 @@ import 'auth_session_cubit_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), MockSpec(), ]) void main() { late MockAuthRepository repository; late MockTokenStorage tokenStorage; late MockFitnessStartProgressStorage progressStorage; - late MockGuestSessionStorage guestSessionStorage; late MockAppLogger logger; late AuthSessionCubit authSessionCubit; @@ -39,20 +36,17 @@ void main() { repository = MockAuthRepository(); tokenStorage = MockTokenStorage(); progressStorage = MockFitnessStartProgressStorage(); - guestSessionStorage = MockGuestSessionStorage(); logger = MockAppLogger(); authSessionCubit = AuthSessionCubit( repository, tokenStorage, progressStorage, - guestSessionStorage, logger, ); provideDummy>(const Success(user)); when(progressStorage.hasCompletedProgress()).thenAnswer((_) async => false); when(progressStorage.saveCompleted()).thenAnswer((_) async {}); when(progressStorage.clear()).thenAnswer((_) async {}); - when(guestSessionStorage.clear()).thenAnswer((_) async {}); }); group('AuthSessionCubit', () { @@ -69,7 +63,6 @@ void main() { verify(tokenStorage.getAccessToken()).called(1); verify(progressStorage.hasCompletedProgress()).called(1); verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verifyNever(repository.getCurrentUser()); }, ); @@ -90,7 +83,6 @@ void main() { verify(tokenStorage.getAccessToken()).called(1); verify(progressStorage.hasCompletedProgress()).called(1); verifyNever(progressStorage.clear()); - verifyNever(guestSessionStorage.clear()); verifyNever(repository.getCurrentUser()); }, ); @@ -112,7 +104,6 @@ void main() { verify(progressStorage.hasCompletedProgress()).called(1); verify(logger.e(any, any, any)).called(1); verifyNever(progressStorage.clear()); - verifyNever(guestSessionStorage.clear()); verifyNever(repository.getCurrentUser()); }, ); @@ -133,7 +124,6 @@ void main() { verify(tokenStorage.getAccessToken()).called(1); verify(repository.getCurrentUser()).called(1); verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verifyNever(progressStorage.hasCompletedProgress()); verifyNever(tokenStorage.deleteAccessToken()); }, @@ -159,7 +149,6 @@ void main() { verify(repository.getCurrentUser()).called(1); verify(tokenStorage.deleteAccessToken()).called(1); verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); }, ); @@ -273,7 +262,6 @@ void main() { expect: () => const [AuthSessionState.guest()], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); }, ); @@ -288,7 +276,6 @@ void main() { expect: () => const [], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verify(logger.e(any, any, any)).called(1); }, ); @@ -301,7 +288,6 @@ void main() { expect: () => const [], verify: (_) { verifyNever(progressStorage.clear()); - verifyNever(guestSessionStorage.clear()); }, ); @@ -351,14 +337,13 @@ void main() { expect: () => const [AuthSessionState.unauthenticated()], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); }, ); blocTest( - 'cancelGuestFlow does not emit unauthenticated when guest session clear fails', + 'cancelGuestFlow does not emit unauthenticated when guest progress clear fails', setUp: () { - when(guestSessionStorage.clear()).thenThrow(Exception('storage_error')); + when(progressStorage.clear()).thenThrow(Exception('storage_error')); }, build: () => authSessionCubit, seed: () => const AuthSessionState.guest(), @@ -366,7 +351,6 @@ void main() { expect: () => const [], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verify(logger.e(any, any, any)).called(1); }, ); @@ -381,7 +365,6 @@ void main() { expect: () => const [AuthSessionState.authenticated(user)], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); }, ); @@ -392,7 +375,6 @@ void main() { expect: () => const [AuthSessionState.unauthenticated()], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verifyNever(repository.getCurrentUser()); verifyNever(tokenStorage.deleteAccessToken()); }, @@ -408,7 +390,6 @@ void main() { expect: () => const [AuthSessionState.unauthenticated()], verify: (_) { verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verify(logger.e(any, any, any)).called(1); verify(logger.w(any, any, any)).called(1); }, @@ -425,7 +406,6 @@ void main() { verify: (_) { verify(tokenStorage.deleteAccessToken()).called(1); verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); }, ); @@ -440,7 +420,6 @@ void main() { verify: (_) { verify(tokenStorage.deleteAccessToken()).called(1); verify(progressStorage.clear()).called(1); - verify(guestSessionStorage.clear()).called(1); verify(logger.e(any, any, any)).called(1); }, ); From b43635540cd4dc13afea397146e669febfa66f25 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 14 May 2026 16:42:35 +0700 Subject: [PATCH 4/7] docs: update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f549d47..2016bf1d 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Mobile client for the moveUP fitness platform. ## 🚀 Highlights -- 🏃 **Fitness Start Onboarding**: guest quiz, onboarding test catalog, guest test attempts, and completed-progress resume support before sign-up +- 🏃 **Fitness Start Onboarding**: guest quiz and resume support before sign-up - 🔐 **End-to-end Auth Flow**: sign in, sign up, email verification, password recovery, OTP verification, password reset, and logout - 📶 **Offline Flow**: app-level connectivity tracking, blocking offline route, and automatic return to the auth flow after reconnect - ✨ **Startup Splash Flow**: branded startup entry route with router-driven splash timing before auth or offline redirects @@ -98,7 +98,7 @@ lib/ │ │ ├── data/ # DTOs, API client, repository implementation │ │ ├── domain/ # Entities and repository contracts │ │ └── presentation/ # Pages, widgets, cubits, validators -│ ├── fitness_start/ # Guest onboarding quiz, tests shell, and onboarding flow UI +│ ├── fitness_start/ # Guest onboarding quiz │ ├── offline/ # Connectivity state and blocking offline flow │ ├── splash/ # Branded startup splash screen │ ├── tests/ # Shared tests catalog and guest test attempt flow From 914e9a7fc958f640db5e423eba7e2b11babf0f12 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 14 May 2026 17:39:40 +0700 Subject: [PATCH 5/7] docs: update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af5402e2..ec8bec46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,11 +46,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The debug route is now a static centered placeholder again and no longer owns a separate logout flow. - `AppCard` now supports an optional fixed height, allowing specialized screens like the subscriptions catalog to match exact card mockups without introducing a forked card component. - Subscriptions catalog cards now open a dedicated subscription details screen, and `AppInputField` now supports hidden labels for grouped payment-field layouts without requiring widget forks. +- Removed guest test attempt flow, onboarding testing routes, testing-related string constants from fitness start onboarding. ### Breaking - Shared test-attempt transport DTOs were renamed from guest-prefixed names to neutral request/response models because the same payload shapes are now reused by both guest and authenticated flows. - Test-attempt DI wiring now resolves separate guest and authenticated repository bindings while keeping the shared `TestAttemptCubit` and domain contract unchanged. +- Guest fitness start onboarding now completes with fitness parameters collection (goals, physical metrics, and fitness level) and proceeds directly to sign-up, removing the intermediate testing step. ### Fixed From f51abc4851d4d405e9968a6d45318c17ae4bc6d2 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Mon, 18 May 2026 14:14:35 +0700 Subject: [PATCH 6/7] docs: update summaries for TestAttemptRepositoryImpl to reflect recent changes --- .../data/repositories/test_attempt_repository_impl.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart b/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart index 66a49512..b534dc0d 100644 --- a/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart +++ b/lib/features/tests/attempt/data/repositories/test_attempt_repository_impl.dart @@ -14,9 +14,9 @@ import '../dto/save_test_result_request_dto.dart'; import '../mappers/test_attempt_mapper.dart'; import 'test_attempt_result_payload_validator.dart'; -/// Authenticated implementation of [AuthenticatedTestAttemptRepository]. +/// Implementation of [TestAttemptRepository]. final class TestAttemptRepositoryImpl implements TestAttemptRepository { - /// Logger for tracking authenticated test attempt operations. + /// Logger for tracking test attempt operations. final AppLogger _logger; /// API client for tests catalog and attempts. @@ -53,7 +53,7 @@ final class TestAttemptRepositoryImpl implements TestAttemptRepository { final response = await _apiClient.saveTestResult(attemptId, request); final payload = response.data; if (!isValidTestAttemptResultPayload(payload)) { - final exception = StateError('Malformed authenticated test result payload.'); + final exception = StateError('Malformed test result payload.'); _logger.e('SaveTestResult returned malformed payload', exception); return Result.failure(UnknownTestsFailure(parentException: exception)); } From b97575ac174638a07e0e63e8eb076e56a79152a9 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Mon, 18 May 2026 14:15:41 +0700 Subject: [PATCH 7/7] docs: update README.md to reflect recent changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2016bf1d..6682a805 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ lib/ │ ├── fitness_start/ # Guest onboarding quiz │ ├── offline/ # Connectivity state and blocking offline flow │ ├── splash/ # Branded startup splash screen -│ ├── tests/ # Shared tests catalog and guest test attempt flow +│ ├── tests/ # Tests catalog and guest attempt flow │ └── debug/ # Internal debug screen ├── uikit/ │ ├── buttons/ # Shared buttons