- Create auth feature with Clean Architecture (domain/data/presentation) - Add login and register pages with form validation - Implement secure token storage with flutter_secure_storage - Create AuthenticatedClient for automatic Bearer token headers - Add AuthViewModel for global auth state management - Update router with auth guards (redirect to login if not authenticated) - Add logout option to settings page - Update TaskRemoteDataSource to use authenticated client - Add auth-related localization strings (EN/DE)
161 lines
3.6 KiB
Dart
161 lines
3.6 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import '../../../../core/errors/failures.dart';
|
|
import '../../../../core/logging/app_logger.dart';
|
|
import '../../domain/entities/user_entity.dart';
|
|
import '../../domain/repositories/auth_repository.dart';
|
|
|
|
enum AuthState {
|
|
initial,
|
|
loading,
|
|
authenticated,
|
|
unauthenticated,
|
|
}
|
|
|
|
class AuthViewModel extends ChangeNotifier {
|
|
final AuthRepository repository;
|
|
final AppLogger logger;
|
|
|
|
AuthState _state = AuthState.initial;
|
|
UserEntity? _user;
|
|
String? _error;
|
|
StreamSubscription<bool>? _authStateSubscription;
|
|
|
|
AuthViewModel({
|
|
required this.repository,
|
|
required this.logger,
|
|
}) {
|
|
_init();
|
|
}
|
|
|
|
AuthState get state => _state;
|
|
UserEntity? get user => _user;
|
|
String? get error => _error;
|
|
bool get isAuthenticated => _state == AuthState.authenticated;
|
|
bool get isLoading => _state == AuthState.loading;
|
|
|
|
Future<void> _init() async {
|
|
_authStateSubscription = repository.authStateChanges.listen((isAuth) {
|
|
if (!isAuth && _state == AuthState.authenticated) {
|
|
_state = AuthState.unauthenticated;
|
|
_user = null;
|
|
notifyListeners();
|
|
}
|
|
});
|
|
|
|
await checkAuthStatus();
|
|
}
|
|
|
|
Future<void> checkAuthStatus() async {
|
|
final isAuth = await repository.isAuthenticated();
|
|
if (isAuth) {
|
|
final result = await repository.getCurrentUser();
|
|
result.when(
|
|
success: (user) {
|
|
_user = user;
|
|
_state = AuthState.authenticated;
|
|
},
|
|
error: (_) {
|
|
_state = AuthState.unauthenticated;
|
|
},
|
|
);
|
|
} else {
|
|
_state = AuthState.unauthenticated;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<bool> register({
|
|
required String email,
|
|
required String password,
|
|
required String name,
|
|
}) async {
|
|
_state = AuthState.loading;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
final result = await repository.register(
|
|
email: email,
|
|
password: password,
|
|
name: name,
|
|
);
|
|
|
|
return result.when(
|
|
success: (user) {
|
|
_state = AuthState.unauthenticated;
|
|
notifyListeners();
|
|
return true;
|
|
},
|
|
error: (failure) {
|
|
_error = _getErrorMessage(failure);
|
|
_state = AuthState.unauthenticated;
|
|
notifyListeners();
|
|
return false;
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<bool> login({
|
|
required String email,
|
|
required String password,
|
|
}) async {
|
|
_state = AuthState.loading;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
final result = await repository.login(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
|
|
return result.when(
|
|
success: (user) {
|
|
_user = user;
|
|
_state = AuthState.authenticated;
|
|
notifyListeners();
|
|
return true;
|
|
},
|
|
error: (failure) {
|
|
_error = _getErrorMessage(failure);
|
|
_state = AuthState.unauthenticated;
|
|
notifyListeners();
|
|
return false;
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
_state = AuthState.loading;
|
|
notifyListeners();
|
|
|
|
await repository.logout();
|
|
_user = null;
|
|
_state = AuthState.unauthenticated;
|
|
notifyListeners();
|
|
}
|
|
|
|
void clearError() {
|
|
_error = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
String _getErrorMessage(Failure failure) {
|
|
if (failure is AuthFailure) {
|
|
return failure.message;
|
|
} else if (failure is NetworkFailure) {
|
|
return 'No internet connection';
|
|
} else if (failure is ServerFailure) {
|
|
return 'Server error. Please try again.';
|
|
}
|
|
return 'An unexpected error occurred';
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_authStateSubscription?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|