m3mo d8164be49a Add user authentication to Flutter frontend
- 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)
2026-02-02 22:58:07 +01:00

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();
}
}