refactor(legacy_auth): migrate login to Riverpod

This commit is contained in:
2026-04-22 23:21:44 +02:00
parent c17e94ff7f
commit 76782fbfd4
6 changed files with 263 additions and 225 deletions

View File

@@ -1,13 +1,14 @@
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:legacy_auth/src/features/login/presentation/state/login_view_model.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/field_error_text.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/two_factor_sheet_launcher.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_auth/src/features/login/presentation/providers/login_controller.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/field_error_text.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/two_factor_sheet_launcher.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
String _authErrorI18nKey(LegacyAuthErrorEvent event) {
return switch (event) {
@@ -24,19 +25,62 @@ String _authErrorI18nKey(LegacyAuthErrorEvent event) {
};
}
class LegacyLoginScreen extends ConsumerWidget {
class LegacyLoginScreen extends ConsumerStatefulWidget {
final NavigationContract navigationContract;
const LegacyLoginScreen({super.key, required this.navigationContract});
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isLoading = ref.watch(
legacyLoginViewModelProvider.select((s) => s.isLoading),
);
ConsumerState<LegacyLoginScreen> createState() => _LegacyLoginScreenState();
}
class _LegacyLoginScreenState extends ConsumerState<LegacyLoginScreen> {
late final TextEditingController _emailController;
late final TextEditingController _passwordController;
@override
void initState() {
super.initState();
final initial = ref.read(loginControllerProvider);
_emailController = TextEditingController(text: initial.email);
_passwordController = TextEditingController(text: initial.password);
_emailController.addListener(_onEmailChanged);
_passwordController.addListener(_onPasswordChanged);
}
@override
void dispose() {
_emailController.removeListener(_onEmailChanged);
_passwordController.removeListener(_onPasswordChanged);
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _onEmailChanged() {
ref
.read(loginControllerProvider.notifier)
.setEmail(_emailController.text);
}
void _onPasswordChanged() {
ref
.read(loginControllerProvider.notifier)
.setPassword(_passwordController.text);
}
void _submit() {
FocusManager.instance.primaryFocus?.unfocus();
ref.read(loginControllerProvider.notifier).login();
}
@override
Widget build(BuildContext context) {
final isLoading =
ref.watch(loginControllerProvider.select((s) => s.isLoading));
ref.listen(
legacyLoginViewModelProvider.select(
loginControllerProvider.select(
(s) => (
errorEvent: s.errorEvent,
twoFA: s.twoFARequested,
@@ -44,29 +88,20 @@ class LegacyLoginScreen extends ConsumerWidget {
hasDevices: s.hasDevices,
),
),
(previous, next) {
(previous, next) async {
final errorEvent = next.errorEvent;
if (errorEvent != null && !next.twoFA) {
showTopSnackbar(
context,
message: context.translate(_authErrorI18nKey(errorEvent)),
type: MessageType.error,
);
ref.read(legacyLoginViewModelProvider.notifier).clearErrorEvent();
await showErrorDialog(context, _authErrorI18nKey(errorEvent));
ref.read(loginControllerProvider.notifier).clearErrorEvent();
}
if (next.twoFA && previous?.twoFA != true) {
showTwoFactorSheet(context);
if (context.mounted) showTwoFactorSheet(context);
}
if (next.verified) {
showTopSnackbar(
context,
message: context.translate(I18n.loginSuccess),
type: MessageType.success,
);
if (next.hasDevices) {
navigationContract.goTo(AppRoutes.controlPanel);
widget.navigationContract.goTo(AppRoutes.controlPanel);
} else {
navigationContract.goTo(AppRoutes.legacyDeviceSetup);
widget.navigationContract.goTo(AppRoutes.legacyDeviceSetup);
}
}
},
@@ -78,31 +113,22 @@ class LegacyLoginScreen extends ConsumerWidget {
child: AbsorbPointer(
absorbing: isLoading,
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24),
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _Header(),
SizedBox(height: 48),
_EmailSection(),
SizedBox(height: 24),
const SizedBox(height: 48),
_EmailSection(controller: _emailController),
const SizedBox(height: 24),
_PasswordSection(
onSubmitted: () {
FocusManager.instance.primaryFocus?.unfocus();
ref.read(legacyLoginViewModelProvider.notifier).login();
},
controller: _passwordController,
onSubmitted: _submit,
),
SizedBox(height: 16),
// _ForgotPassword(navigationContract: navigationContract),
// SizedBox(height: 30),
_SignInSection(
onSignIn: () {
FocusManager.instance.primaryFocus?.unfocus();
ref.read(legacyLoginViewModelProvider.notifier).login();
},
),
SizedBox(height: 30),
_Footer(navigationContract: navigationContract),
const SizedBox(height: 16),
_SignInSection(onSignIn: _submit),
const SizedBox(height: 30),
_Footer(navigationContract: widget.navigationContract),
],
),
),
@@ -135,14 +161,13 @@ class _Header extends StatelessWidget {
}
class _EmailSection extends ConsumerWidget {
const _EmailSection();
const _EmailSection({required this.controller});
final TextEditingController controller;
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.read(legacyLoginViewModelProvider.notifier);
final String emailErrorKey = ref.watch(
legacyLoginViewModelProvider.select((s) => s.emailError),
);
final emailErrorKey =
ref.watch(loginControllerProvider.select((s) => s.emailError));
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -150,7 +175,7 @@ class _EmailSection extends ConsumerWidget {
CustomTextField(
hint: context.translate(I18n.username),
label: context.translate(I18n.username),
controller: vm.emailController,
controller: controller,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
@@ -161,18 +186,19 @@ class _EmailSection extends ConsumerWidget {
}
class _PasswordSection extends ConsumerWidget {
const _PasswordSection({required this.onSubmitted});
const _PasswordSection({
required this.controller,
required this.onSubmitted,
});
final TextEditingController controller;
final VoidCallback onSubmitted;
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.read(legacyLoginViewModelProvider.notifier);
final bool passwordVisible = ref.watch(
legacyLoginViewModelProvider.select((s) => s.passwordVisible),
);
final String passwordErrorKey = ref.watch(
legacyLoginViewModelProvider.select((s) => s.passwordError),
);
final passwordVisible =
ref.watch(loginControllerProvider.select((s) => s.passwordVisible));
final passwordErrorKey =
ref.watch(loginControllerProvider.select((s) => s.passwordError));
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -181,7 +207,7 @@ class _PasswordSection extends ConsumerWidget {
showPassword: passwordVisible,
label: context.translate(I18n.password),
hint: '********',
controller: vm.passwordController,
controller: controller,
textInputAction: TextInputAction.done,
onSubmitted: (_) => onSubmitted(),
),
@@ -191,15 +217,15 @@ class _PasswordSection extends ConsumerWidget {
}
}
// ignore: unused_element
class _ForgotPassword extends ConsumerWidget {
const _ForgotPassword({required this.navigationContract});
final NavigationContract navigationContract;
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isLoading = ref.watch(
legacyLoginViewModelProvider.select((s) => s.isLoading),
);
final isLoading =
ref.watch(loginControllerProvider.select((s) => s.isLoading));
return Align(
alignment: Alignment.topLeft,
@@ -221,9 +247,8 @@ class _SignInSection extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isLoading = ref.watch(
legacyLoginViewModelProvider.select((s) => s.isLoading),
);
final isLoading =
ref.watch(loginControllerProvider.select((s) => s.isLoading));
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -254,9 +279,8 @@ class _Footer extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isLoading = ref.watch(
legacyLoginViewModelProvider.select((s) => s.isLoading),
);
final isLoading =
ref.watch(loginControllerProvider.select((s) => s.isLoading));
return Column(
children: [

View File

@@ -1,65 +1,40 @@
import 'dart:async';
import 'package:legacy_auth/src/core/domain/repositories/login_repository.dart';
import 'package:flutter/widgets.dart';
import 'package:legacy_auth/src/core/providers/login_repository_provider.dart';
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_auth/src/features/login/presentation/mixins/login_form_validation.dart';
import 'package:legacy_auth/src/features/login/presentation/state/login_view_state.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_auth/src/features/login/presentation/providers/login_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
final legacyLoginViewModelProvider =
NotifierProvider.autoDispose<LegacyLoginViewModel, LegacyLoginViewState>(
LegacyLoginViewModel.new,
);
part 'login_controller.g.dart';
class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
with LoginFormValidation {
late final LegacyLoginRepository _repository;
late final GetUserInfoUseCase _getUserInfoUseCase;
late final NotificationsRemoteDatasource _notifications;
late final SfTrackingRepository _tracking;
late final TextEditingController emailController;
late final TextEditingController passwordController;
const int resendCooldownSeconds = 30;
@Riverpod(keepAlive: true)
class LoginController extends _$LoginController with LoginFormValidation {
Timer? _cooldownTimer;
static const int resendCooldownSeconds = 30;
@override
LegacyLoginViewState build() {
_repository = ref.read(legacyLoginRepositoryProvider);
_getUserInfoUseCase = ref.read(getUserInfoUseCaseProvider);
_notifications = ref.read(notificationsRemoteDatasourceProvider);
_tracking = ref.read(sfTrackingProvider);
emailController = TextEditingController();
emailController.addListener(_onEmailChanged);
passwordController = TextEditingController();
passwordController.addListener(_onPasswordChanged);
ref.onDispose(_dispose);
return const LegacyLoginViewState();
LoginState build() {
ref.onDispose(() => _cooldownTimer?.cancel());
return const LoginState();
}
void _onEmailChanged() {
final value = emailController.text;
void setEmail(String value) {
if (value == state.email) return;
state = state.copyWith(
email: value,
errorEvent: null,
emailError: state.showErrors ? validateEmail(value) : state.emailError,
emailError:
state.showErrors ? validateEmail(value) : state.emailError,
);
}
void _onPasswordChanged() {
final value = passwordController.text;
void setPassword(String value) {
if (value == state.password) return;
state = state.copyWith(
password: value,
@@ -70,14 +45,6 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
);
}
void _dispose() {
_cooldownTimer?.cancel();
emailController.removeListener(_onEmailChanged);
emailController.dispose();
passwordController.removeListener(_onPasswordChanged);
passwordController.dispose();
}
void togglePasswordVisible() {
state = state.copyWith(passwordVisible: !state.passwordVisible);
}
@@ -105,6 +72,8 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
final email = state.email.trim();
final password = state.password.trim();
final repository = ref.read(legacyLoginRepositoryProvider);
final tracking = ref.read(sfTrackingProvider);
state = state.copyWith(
isLoading: true,
@@ -112,17 +81,12 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
twoFAVerified: false,
);
unawaited(_tracking.legacyAuthLoginAttempt());
unawaited(tracking.legacyAuthLoginAttempt());
try {
final response = await _repository.login(
email: email,
password: password,
);
final response = await repository.login(email: email, password: password);
if (!ref.mounted) return;
unawaited(_tracking.legacyAuthLoginSuccess());
unawaited(tracking.legacyAuthLoginSuccess());
state = state.copyWith(
token: response.token,
@@ -130,10 +94,7 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
code: '',
);
} catch (e) {
if (!ref.mounted) return;
unawaited(_tracking.legacyAuthLoginFailure(formatErrorMessage(e)));
unawaited(tracking.legacyAuthLoginFailure(formatErrorMessage(e)));
state = state.copyWith(
isLoading: false,
errorEvent: mapLoginError(e),
@@ -161,15 +122,16 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
return;
}
final repository = ref.read(legacyLoginRepositoryProvider);
final tracking = ref.read(sfTrackingProvider);
try {
await _repository.twoFARequestCode(
await repository.twoFARequestCode(
token: state.token,
methodType: state.availableMethods.first.methodType,
);
if (!ref.mounted) return;
unawaited(_tracking.legacyAuth2faRequested());
unawaited(tracking.legacyAuth2faRequested());
_startResendCooldown();
state = state.copyWith(
@@ -177,8 +139,6 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
isLoading: updateLoading ? false : state.isLoading,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
errorEvent: mapTwoFactorError(e),
isLoading: updateLoading ? false : state.isLoading,
@@ -214,32 +174,31 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
orElse: () => state.availableMethods.first,
);
final repository = ref.read(legacyLoginRepositoryProvider);
final tracking = ref.read(sfTrackingProvider);
final getUserInfoUseCase = ref.read(getUserInfoUseCaseProvider);
final notifications = ref.read(notificationsRemoteDatasourceProvider);
state = state.copyWith(isLoading: true, errorEvent: null, codeError: '');
try {
await _repository.twoFASendCode(
await repository.twoFASendCode(
token: state.token,
code: state.code.trim(),
methodType: method.methodType,
);
if (!ref.mounted) return;
unawaited(tracking.legacyAuth2faVerified());
unawaited(_tracking.legacyAuth2faVerified());
await _getUserInfoUseCase.getUserInfo();
if (!ref.mounted) return;
await getUserInfoUseCase.getUserInfo();
unawaited(
_notifications.registerCurrentPushToken().catchError((Object e) {
notifications.registerCurrentPushToken().catchError((Object e) {
debugPrint('[FCM] failed to register push token post-login: $e');
}),
);
final hasDevices = await _repository.hasDevices();
if (!ref.mounted) return;
final hasDevices = await repository.hasDevices();
state = state.copyWith(
isLoading: false,
@@ -247,10 +206,7 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
hasDevices: hasDevices,
);
} catch (e) {
if (!ref.mounted) return;
unawaited(_tracking.legacyAuth2faFailure(formatErrorMessage(e)));
unawaited(tracking.legacyAuth2faFailure(formatErrorMessage(e)));
state = state.copyWith(
isLoading: false,
errorEvent: mapTwoFactorError(e),
@@ -259,7 +215,7 @@ class LegacyLoginViewModel extends Notifier<LegacyLoginViewState>
}
Future<void> resendCode() async {
unawaited(_tracking.legacyAuth2faResend());
unawaited(ref.read(sfTrackingProvider).legacyAuth2faResend());
state = state.copyWith(code: '', isLoading: true);
await _requestTwoFACode(updateLoading: true);
}

View File

@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'login_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(LoginController)
const loginControllerProvider = LoginControllerProvider._();
final class LoginControllerProvider
extends $NotifierProvider<LoginController, LoginState> {
const LoginControllerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'loginControllerProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$loginControllerHash();
@$internal
@override
LoginController create() => LoginController();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(LoginState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<LoginState>(value),
);
}
}
String _$loginControllerHash() => r'991fa84bd8aba5a53640ca0443163f54de175e45';
abstract class _$LoginController extends $Notifier<LoginState> {
LoginState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<LoginState, LoginState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<LoginState, LoginState>,
LoginState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -1,12 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_auth/src/features/login/domain/entities/login_response_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login_view_state.freezed.dart';
part 'login_state.freezed.dart';
@freezed
abstract class LegacyLoginViewState with _$LegacyLoginViewState {
const factory LegacyLoginViewState({
abstract class LoginState with _$LoginState {
const factory LoginState({
@Default('') String email,
@Default('') String password,
@Default(false) bool passwordVisible,
@@ -24,5 +24,5 @@ abstract class LegacyLoginViewState with _$LegacyLoginViewState {
@Default(0) int resendCooldown,
@Default(false) bool twoFAVerified,
@Default(false) bool hasDevices,
}) = _LegacyLoginViewState;
}) = _LoginState;
}

View File

@@ -3,7 +3,7 @@
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'login_view_state.dart';
part of 'login_state.dart';
// **************************************************************************
// FreezedGenerator
@@ -12,20 +12,20 @@ part of 'login_view_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LegacyLoginViewState {
mixin _$LoginState {
String get email; String get password; bool get passwordVisible; String get emailError; String get passwordError; LegacyAuthErrorEvent? get errorEvent; bool get showErrors; bool get isLoading; List<LegacyAvailableMethodEntity> get availableMethods; String get token; bool get twoFARequested; String get code; String get codeError; int get resendCooldown; bool get twoFAVerified; bool get hasDevices;
/// Create a copy of LegacyLoginViewState
/// Create a copy of LoginState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LegacyLoginViewStateCopyWith<LegacyLoginViewState> get copyWith => _$LegacyLoginViewStateCopyWithImpl<LegacyLoginViewState>(this as LegacyLoginViewState, _$identity);
$LoginStateCopyWith<LoginState> get copyWith => _$LoginStateCopyWithImpl<LoginState>(this as LoginState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacyLoginViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other.availableMethods, availableMethods)&&(identical(other.token, token) || other.token == token)&&(identical(other.twoFARequested, twoFARequested) || other.twoFARequested == twoFARequested)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeError, codeError) || other.codeError == codeError)&&(identical(other.resendCooldown, resendCooldown) || other.resendCooldown == resendCooldown)&&(identical(other.twoFAVerified, twoFAVerified) || other.twoFAVerified == twoFAVerified)&&(identical(other.hasDevices, hasDevices) || other.hasDevices == hasDevices));
return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other.availableMethods, availableMethods)&&(identical(other.token, token) || other.token == token)&&(identical(other.twoFARequested, twoFARequested) || other.twoFARequested == twoFARequested)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeError, codeError) || other.codeError == codeError)&&(identical(other.resendCooldown, resendCooldown) || other.resendCooldown == resendCooldown)&&(identical(other.twoFAVerified, twoFAVerified) || other.twoFAVerified == twoFAVerified)&&(identical(other.hasDevices, hasDevices) || other.hasDevices == hasDevices));
}
@@ -34,15 +34,15 @@ int get hashCode => Object.hash(runtimeType,email,password,passwordVisible,email
@override
String toString() {
return 'LegacyLoginViewState(email: $email, password: $password, passwordVisible: $passwordVisible, emailError: $emailError, passwordError: $passwordError, errorEvent: $errorEvent, showErrors: $showErrors, isLoading: $isLoading, availableMethods: $availableMethods, token: $token, twoFARequested: $twoFARequested, code: $code, codeError: $codeError, resendCooldown: $resendCooldown, twoFAVerified: $twoFAVerified, hasDevices: $hasDevices)';
return 'LoginState(email: $email, password: $password, passwordVisible: $passwordVisible, emailError: $emailError, passwordError: $passwordError, errorEvent: $errorEvent, showErrors: $showErrors, isLoading: $isLoading, availableMethods: $availableMethods, token: $token, twoFARequested: $twoFARequested, code: $code, codeError: $codeError, resendCooldown: $resendCooldown, twoFAVerified: $twoFAVerified, hasDevices: $hasDevices)';
}
}
/// @nodoc
abstract mixin class $LegacyLoginViewStateCopyWith<$Res> {
factory $LegacyLoginViewStateCopyWith(LegacyLoginViewState value, $Res Function(LegacyLoginViewState) _then) = _$LegacyLoginViewStateCopyWithImpl;
abstract mixin class $LoginStateCopyWith<$Res> {
factory $LoginStateCopyWith(LoginState value, $Res Function(LoginState) _then) = _$LoginStateCopyWithImpl;
@useResult
$Res call({
String email, String password, bool passwordVisible, String emailError, String passwordError, LegacyAuthErrorEvent? errorEvent, bool showErrors, bool isLoading, List<LegacyAvailableMethodEntity> availableMethods, String token, bool twoFARequested, String code, String codeError, int resendCooldown, bool twoFAVerified, bool hasDevices
@@ -53,14 +53,14 @@ $Res call({
}
/// @nodoc
class _$LegacyLoginViewStateCopyWithImpl<$Res>
implements $LegacyLoginViewStateCopyWith<$Res> {
_$LegacyLoginViewStateCopyWithImpl(this._self, this._then);
class _$LoginStateCopyWithImpl<$Res>
implements $LoginStateCopyWith<$Res> {
_$LoginStateCopyWithImpl(this._self, this._then);
final LegacyLoginViewState _self;
final $Res Function(LegacyLoginViewState) _then;
final LoginState _self;
final $Res Function(LoginState) _then;
/// Create a copy of LegacyLoginViewState
/// Create a copy of LoginState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? email = null,Object? password = null,Object? passwordVisible = null,Object? emailError = null,Object? passwordError = null,Object? errorEvent = freezed,Object? showErrors = null,Object? isLoading = null,Object? availableMethods = null,Object? token = null,Object? twoFARequested = null,Object? code = null,Object? codeError = null,Object? resendCooldown = null,Object? twoFAVerified = null,Object? hasDevices = null,}) {
return _then(_self.copyWith(
@@ -87,8 +87,8 @@ as bool,
}
/// Adds pattern-matching-related methods to [LegacyLoginViewState].
extension LegacyLoginViewStatePatterns on LegacyLoginViewState {
/// Adds pattern-matching-related methods to [LoginState].
extension LoginStatePatterns on LoginState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
@@ -101,10 +101,10 @@ extension LegacyLoginViewStatePatterns on LegacyLoginViewState {
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LegacyLoginViewState value)? $default,{required TResult orElse(),}){
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LoginState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LegacyLoginViewState() when $default != null:
case _LoginState() when $default != null:
return $default(_that);case _:
return orElse();
@@ -123,10 +123,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LegacyLoginViewState value) $default,){
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LoginState value) $default,){
final _that = this;
switch (_that) {
case _LegacyLoginViewState():
case _LoginState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
@@ -144,10 +144,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LegacyLoginViewState value)? $default,){
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LoginState value)? $default,){
final _that = this;
switch (_that) {
case _LegacyLoginViewState() when $default != null:
case _LoginState() when $default != null:
return $default(_that);case _:
return null;
@@ -167,7 +167,7 @@ return $default(_that);case _:
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String email, String password, bool passwordVisible, String emailError, String passwordError, LegacyAuthErrorEvent? errorEvent, bool showErrors, bool isLoading, List<LegacyAvailableMethodEntity> availableMethods, String token, bool twoFARequested, String code, String codeError, int resendCooldown, bool twoFAVerified, bool hasDevices)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LegacyLoginViewState() when $default != null:
case _LoginState() when $default != null:
return $default(_that.email,_that.password,_that.passwordVisible,_that.emailError,_that.passwordError,_that.errorEvent,_that.showErrors,_that.isLoading,_that.availableMethods,_that.token,_that.twoFARequested,_that.code,_that.codeError,_that.resendCooldown,_that.twoFAVerified,_that.hasDevices);case _:
return orElse();
@@ -188,7 +188,7 @@ return $default(_that.email,_that.password,_that.passwordVisible,_that.emailErro
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String email, String password, bool passwordVisible, String emailError, String passwordError, LegacyAuthErrorEvent? errorEvent, bool showErrors, bool isLoading, List<LegacyAvailableMethodEntity> availableMethods, String token, bool twoFARequested, String code, String codeError, int resendCooldown, bool twoFAVerified, bool hasDevices) $default,) {final _that = this;
switch (_that) {
case _LegacyLoginViewState():
case _LoginState():
return $default(_that.email,_that.password,_that.passwordVisible,_that.emailError,_that.passwordError,_that.errorEvent,_that.showErrors,_that.isLoading,_that.availableMethods,_that.token,_that.twoFARequested,_that.code,_that.codeError,_that.resendCooldown,_that.twoFAVerified,_that.hasDevices);case _:
throw StateError('Unexpected subclass');
@@ -208,7 +208,7 @@ return $default(_that.email,_that.password,_that.passwordVisible,_that.emailErro
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String email, String password, bool passwordVisible, String emailError, String passwordError, LegacyAuthErrorEvent? errorEvent, bool showErrors, bool isLoading, List<LegacyAvailableMethodEntity> availableMethods, String token, bool twoFARequested, String code, String codeError, int resendCooldown, bool twoFAVerified, bool hasDevices)? $default,) {final _that = this;
switch (_that) {
case _LegacyLoginViewState() when $default != null:
case _LoginState() when $default != null:
return $default(_that.email,_that.password,_that.passwordVisible,_that.emailError,_that.passwordError,_that.errorEvent,_that.showErrors,_that.isLoading,_that.availableMethods,_that.token,_that.twoFARequested,_that.code,_that.codeError,_that.resendCooldown,_that.twoFAVerified,_that.hasDevices);case _:
return null;
@@ -220,8 +220,8 @@ return $default(_that.email,_that.password,_that.passwordVisible,_that.emailErro
/// @nodoc
class _LegacyLoginViewState implements LegacyLoginViewState {
const _LegacyLoginViewState({this.email = '', this.password = '', this.passwordVisible = false, this.emailError = '', this.passwordError = '', this.errorEvent, this.showErrors = false, this.isLoading = false, final List<LegacyAvailableMethodEntity> availableMethods = const <LegacyAvailableMethodEntity>[], this.token = '', this.twoFARequested = false, this.code = '', this.codeError = '', this.resendCooldown = 0, this.twoFAVerified = false, this.hasDevices = false}): _availableMethods = availableMethods;
class _LoginState implements LoginState {
const _LoginState({this.email = '', this.password = '', this.passwordVisible = false, this.emailError = '', this.passwordError = '', this.errorEvent, this.showErrors = false, this.isLoading = false, final List<LegacyAvailableMethodEntity> availableMethods = const <LegacyAvailableMethodEntity>[], this.token = '', this.twoFARequested = false, this.code = '', this.codeError = '', this.resendCooldown = 0, this.twoFAVerified = false, this.hasDevices = false}): _availableMethods = availableMethods;
@override@JsonKey() final String email;
@@ -247,17 +247,17 @@ class _LegacyLoginViewState implements LegacyLoginViewState {
@override@JsonKey() final bool twoFAVerified;
@override@JsonKey() final bool hasDevices;
/// Create a copy of LegacyLoginViewState
/// Create a copy of LoginState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LegacyLoginViewStateCopyWith<_LegacyLoginViewState> get copyWith => __$LegacyLoginViewStateCopyWithImpl<_LegacyLoginViewState>(this, _$identity);
_$LoginStateCopyWith<_LoginState> get copyWith => __$LoginStateCopyWithImpl<_LoginState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacyLoginViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other._availableMethods, _availableMethods)&&(identical(other.token, token) || other.token == token)&&(identical(other.twoFARequested, twoFARequested) || other.twoFARequested == twoFARequested)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeError, codeError) || other.codeError == codeError)&&(identical(other.resendCooldown, resendCooldown) || other.resendCooldown == resendCooldown)&&(identical(other.twoFAVerified, twoFAVerified) || other.twoFAVerified == twoFAVerified)&&(identical(other.hasDevices, hasDevices) || other.hasDevices == hasDevices));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other._availableMethods, _availableMethods)&&(identical(other.token, token) || other.token == token)&&(identical(other.twoFARequested, twoFARequested) || other.twoFARequested == twoFARequested)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeError, codeError) || other.codeError == codeError)&&(identical(other.resendCooldown, resendCooldown) || other.resendCooldown == resendCooldown)&&(identical(other.twoFAVerified, twoFAVerified) || other.twoFAVerified == twoFAVerified)&&(identical(other.hasDevices, hasDevices) || other.hasDevices == hasDevices));
}
@@ -266,15 +266,15 @@ int get hashCode => Object.hash(runtimeType,email,password,passwordVisible,email
@override
String toString() {
return 'LegacyLoginViewState(email: $email, password: $password, passwordVisible: $passwordVisible, emailError: $emailError, passwordError: $passwordError, errorEvent: $errorEvent, showErrors: $showErrors, isLoading: $isLoading, availableMethods: $availableMethods, token: $token, twoFARequested: $twoFARequested, code: $code, codeError: $codeError, resendCooldown: $resendCooldown, twoFAVerified: $twoFAVerified, hasDevices: $hasDevices)';
return 'LoginState(email: $email, password: $password, passwordVisible: $passwordVisible, emailError: $emailError, passwordError: $passwordError, errorEvent: $errorEvent, showErrors: $showErrors, isLoading: $isLoading, availableMethods: $availableMethods, token: $token, twoFARequested: $twoFARequested, code: $code, codeError: $codeError, resendCooldown: $resendCooldown, twoFAVerified: $twoFAVerified, hasDevices: $hasDevices)';
}
}
/// @nodoc
abstract mixin class _$LegacyLoginViewStateCopyWith<$Res> implements $LegacyLoginViewStateCopyWith<$Res> {
factory _$LegacyLoginViewStateCopyWith(_LegacyLoginViewState value, $Res Function(_LegacyLoginViewState) _then) = __$LegacyLoginViewStateCopyWithImpl;
abstract mixin class _$LoginStateCopyWith<$Res> implements $LoginStateCopyWith<$Res> {
factory _$LoginStateCopyWith(_LoginState value, $Res Function(_LoginState) _then) = __$LoginStateCopyWithImpl;
@override @useResult
$Res call({
String email, String password, bool passwordVisible, String emailError, String passwordError, LegacyAuthErrorEvent? errorEvent, bool showErrors, bool isLoading, List<LegacyAvailableMethodEntity> availableMethods, String token, bool twoFARequested, String code, String codeError, int resendCooldown, bool twoFAVerified, bool hasDevices
@@ -285,17 +285,17 @@ $Res call({
}
/// @nodoc
class __$LegacyLoginViewStateCopyWithImpl<$Res>
implements _$LegacyLoginViewStateCopyWith<$Res> {
__$LegacyLoginViewStateCopyWithImpl(this._self, this._then);
class __$LoginStateCopyWithImpl<$Res>
implements _$LoginStateCopyWith<$Res> {
__$LoginStateCopyWithImpl(this._self, this._then);
final _LegacyLoginViewState _self;
final $Res Function(_LegacyLoginViewState) _then;
final _LoginState _self;
final $Res Function(_LoginState) _then;
/// Create a copy of LegacyLoginViewState
/// Create a copy of LoginState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? email = null,Object? password = null,Object? passwordVisible = null,Object? emailError = null,Object? passwordError = null,Object? errorEvent = freezed,Object? showErrors = null,Object? isLoading = null,Object? availableMethods = null,Object? token = null,Object? twoFARequested = null,Object? code = null,Object? codeError = null,Object? resendCooldown = null,Object? twoFAVerified = null,Object? hasDevices = null,}) {
return _then(_LegacyLoginViewState(
return _then(_LoginState(
email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable
as String,passwordVisible: null == passwordVisible ? _self.passwordVisible : passwordVisible // ignore: cast_nullable_to_non_nullable

View File

@@ -1,10 +1,10 @@
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_auth/src/features/login/presentation/state/login_view_model.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/two_factor_bottom_sheet.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_auth/src/features/login/domain/entities/legacy_auth_error_event.dart';
import 'package:legacy_auth/src/features/login/presentation/providers/login_controller.dart';
import 'package:legacy_auth/src/features/login/presentation/widgets/two_factor_bottom_sheet.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
String _twoFactorErrorI18nKey(LegacyAuthErrorEvent event) {
return switch (event) {
@@ -28,43 +28,38 @@ void showTwoFactorSheet(BuildContext context) {
builder: (sheetContext) {
return Consumer(
builder: (sheetContext, ref, _) {
final vm = ref.read(legacyLoginViewModelProvider.notifier);
final notifier = ref.read(loginControllerProvider.notifier);
final otpErrorKey = ref.watch(
legacyLoginViewModelProvider.select((s) => s.codeError),
);
final isLoading = ref.watch(
legacyLoginViewModelProvider.select((s) => s.isLoading),
);
final code = ref.watch(
legacyLoginViewModelProvider.select((s) => s.code),
);
final resendCooldown = ref.watch(
legacyLoginViewModelProvider.select((s) => s.resendCooldown),
);
final otpErrorKey = ref
.watch(loginControllerProvider.select((s) => s.codeError));
final isLoading = ref
.watch(loginControllerProvider.select((s) => s.isLoading));
final code =
ref.watch(loginControllerProvider.select((s) => s.code));
final resendCooldown = ref
.watch(loginControllerProvider.select((s) => s.resendCooldown));
final otpErrorText = otpErrorKey.isEmpty
? ''
: sheetContext.translate(otpErrorKey);
ref.listen(
legacyLoginViewModelProvider.select(
loginControllerProvider.select(
(s) => (errorEvent: s.errorEvent, verified: s.twoFAVerified),
),
(previous, next) {
(previous, next) async {
final errorEvent = next.errorEvent;
if (errorEvent != null && errorEvent != previous?.errorEvent) {
showTopSnackbar(
await showErrorDialog(
sheetContext,
message: sheetContext.translate(
_twoFactorErrorI18nKey(errorEvent),
),
type: MessageType.error,
_twoFactorErrorI18nKey(errorEvent),
);
ref.read(legacyLoginViewModelProvider.notifier).clearErrorEvent();
ref.read(loginControllerProvider.notifier).clearErrorEvent();
}
if (next.verified) {
Navigator.of(sheetContext).pop();
if (sheetContext.mounted) {
Navigator.of(sheetContext).pop();
}
}
},
);
@@ -83,14 +78,14 @@ void showTwoFactorSheet(BuildContext context) {
canResend: canResend,
otpCode: code,
otpErrorText: otpErrorText,
onChanged: vm.setCode,
onChanged: notifier.setCode,
onVerify: () async {
FocusManager.instance.primaryFocus?.unfocus();
await vm.submitTwoFACode();
await notifier.submitTwoFACode();
},
onResend: () => vm.resendCode(),
onResend: () => notifier.resendCode(),
onClose: () {
vm.dismissTwoFA();
notifier.dismissTwoFA();
Navigator.of(sheetContext).pop();
},
);