import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shuffle/core/routing/app_routes.dart'; import 'package:shuffle/features/auth/domain/auth_failure.dart'; import 'package:shuffle/features/auth/domain/resend_origin.dart'; import 'package:shuffle/features/auth/domain/validation/email_format.dart'; import 'package:shuffle/features/auth/presentation/check_email/resend_controller.dart'; import 'package:shuffle/features/auth/presentation/login/login_state.dart'; import 'package:shuffle/features/auth/presentation/providers/auth_providers.dart'; import 'package:shuffle/features/auth/presentation/providers/auth_redirect.dart'; part 'login_controller.g.dart'; /// Logic of the sign-in form (KTO-01 Kriterien 5, 6 und 7). @riverpod class LoginController extends _$LoginController { @override LoginState build() => const LoginState(); void emailChanged(String value) { state = state.copyWith(email: value); } void passwordChanged(String value) { state = state.copyWith(password: value); } /// Field left: show its error. void fieldBlurred(LoginField field) { state = state.copyWith(visibleFields: {...state.visibleFields, field}); } /// Validates and signs in. Returns the path to continue to on success /// (the validated `from` target, otherwise the start page), `null` if the /// user stays on the screen. A second call while one runs does nothing. Future submit({String? from}) async { if (state.isSubmitting) { return null; } state = state.copyWith( status: LoginStatus.submitting, visibleFields: LoginField.values.toSet(), bannerFailure: null, unconfirmedEmail: null, focusField: null, ); // A new attempt starts without the result of an earlier resend. ref.invalidate(resendControllerProvider); final invalid = state.firstInvalidField; if (invalid != null) { state = state.copyWith( status: LoginStatus.editing, focusField: invalid, focusRequest: state.focusRequest + 1, ); return null; } final email = EmailFormat.normalize(state.email); final result = await ref .read(authRepositoryProvider) .signIn(email, state.password); final failure = result.errorOrNull; if (failure == null) { // Success: the target is returned even if the screen is already gone // (the router redirect can remove it before this future completes), so // the caller can still finish the autofill context. The status stays // `submitting`, the form is not reused after a successful sign-in. return safeFromPath(from) ?? AppRoutes.home; } if (!ref.mounted) { return null; } state = state.copyWith( status: LoginStatus.editing, bannerFailure: failure, unconfirmedEmail: failure is EmailNotConfirmed ? email : null, ); return null; } /// "Resend confirmation email" after `emailNotConfirmed` (Kriterium 6). /// The password was right, so a mail-limit answer is shown (F12). Future resendConfirmation() async { final email = state.unconfirmedEmail; if (email == null) { return; } await ref .read(resendControllerProvider.notifier) .resend(email, origin: ResendOrigin.login); } }