252 lines
6.0 KiB
Dart
252 lines
6.0 KiB
Dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import '../../../../core/network/dio_client.dart';
|
|
import '../../../../core/storage/secure_storage.dart';
|
|
import '../../data/datasources/auth_remote_datasource.dart';
|
|
import '../../data/repositories/auth_repository_impl.dart';
|
|
import '../../domain/entities/user.dart';
|
|
import '../../domain/repositories/auth_repository.dart';
|
|
|
|
part 'auth_provider.g.dart';
|
|
|
|
/// Provider for DioClient (singleton)
|
|
@Riverpod(keepAlive: true)
|
|
DioClient dioClient(Ref ref) {
|
|
return DioClient();
|
|
}
|
|
|
|
/// Provider for SecureStorage (singleton)
|
|
@Riverpod(keepAlive: true)
|
|
SecureStorage secureStorage(Ref ref) {
|
|
return SecureStorage();
|
|
}
|
|
|
|
/// Provider for AuthRemoteDataSource
|
|
@Riverpod(keepAlive: true)
|
|
AuthRemoteDataSource authRemoteDataSource(Ref ref) {
|
|
final dioClient = ref.watch(dioClientProvider);
|
|
return AuthRemoteDataSourceImpl(dioClient: dioClient);
|
|
}
|
|
|
|
/// Provider for AuthRepository
|
|
@Riverpod(keepAlive: true)
|
|
AuthRepository authRepository(Ref ref) {
|
|
final remoteDataSource = ref.watch(authRemoteDataSourceProvider);
|
|
final secureStorage = ref.watch(secureStorageProvider);
|
|
final dioClient = ref.watch(dioClientProvider);
|
|
|
|
return AuthRepositoryImpl(
|
|
remoteDataSource: remoteDataSource,
|
|
secureStorage: secureStorage,
|
|
dioClient: dioClient,
|
|
);
|
|
}
|
|
|
|
/// Auth state class
|
|
class AuthState {
|
|
final User? user;
|
|
final bool isAuthenticated;
|
|
final bool isLoading;
|
|
final String? errorMessage;
|
|
|
|
const AuthState({
|
|
this.user,
|
|
this.isAuthenticated = false,
|
|
this.isLoading = false,
|
|
this.errorMessage,
|
|
});
|
|
|
|
AuthState copyWith({
|
|
User? user,
|
|
bool? isAuthenticated,
|
|
bool? isLoading,
|
|
String? errorMessage,
|
|
bool clearUser = false,
|
|
bool clearError = false,
|
|
}) {
|
|
return AuthState(
|
|
user: clearUser ? null : (user ?? this.user),
|
|
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Auth state notifier provider
|
|
@Riverpod(keepAlive: true)
|
|
class Auth extends _$Auth {
|
|
@override
|
|
AuthState build() {
|
|
// Don't call async operations in build
|
|
// Use a separate method to initialize auth state
|
|
return const AuthState();
|
|
}
|
|
|
|
AuthRepository get _repository => ref.read(authRepositoryProvider);
|
|
|
|
/// Initialize auth state - call this on app start
|
|
Future<void> initialize() async {
|
|
state = state.copyWith(isLoading: true);
|
|
|
|
final isAuthenticated = await _repository.isAuthenticated();
|
|
|
|
if (isAuthenticated) {
|
|
// Get user profile
|
|
final result = await _repository.getProfile();
|
|
result.fold(
|
|
(failure) {
|
|
state = const AuthState(
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
);
|
|
},
|
|
(user) {
|
|
state = AuthState(
|
|
user: user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
);
|
|
},
|
|
);
|
|
} else {
|
|
state = const AuthState(
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Login user
|
|
Future<bool> login({
|
|
required String email,
|
|
required String password,
|
|
}) async {
|
|
state = state.copyWith(isLoading: true, clearError: true);
|
|
|
|
final result = await _repository.login(email: email, password: password);
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
state = state.copyWith(
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
errorMessage: failure.message,
|
|
);
|
|
return false;
|
|
},
|
|
(authResponse) {
|
|
state = AuthState(
|
|
user: authResponse.user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
errorMessage: null,
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Register new user
|
|
Future<bool> register({
|
|
required String name,
|
|
required String email,
|
|
required String password,
|
|
List<String> roles = const ['user'],
|
|
}) async {
|
|
state = state.copyWith(isLoading: true, clearError: true);
|
|
|
|
final result = await _repository.register(
|
|
name: name,
|
|
email: email,
|
|
password: password,
|
|
roles: roles,
|
|
);
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
state = state.copyWith(
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
errorMessage: failure.message,
|
|
);
|
|
return false;
|
|
},
|
|
(authResponse) {
|
|
state = AuthState(
|
|
user: authResponse.user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
errorMessage: null,
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Get user profile (refresh user data)
|
|
Future<void> getProfile() async {
|
|
state = state.copyWith(isLoading: true, clearError: true);
|
|
|
|
final result = await _repository.getProfile();
|
|
|
|
result.fold(
|
|
(failure) {
|
|
state = state.copyWith(
|
|
isLoading: false,
|
|
errorMessage: failure.message,
|
|
);
|
|
},
|
|
(user) {
|
|
state = state.copyWith(
|
|
user: user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Refresh access token
|
|
Future<bool> refreshToken() async {
|
|
final result = await _repository.refreshToken();
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
// If token refresh fails, logout user
|
|
logout();
|
|
return false;
|
|
},
|
|
(authResponse) {
|
|
state = state.copyWith(user: authResponse.user);
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Logout user
|
|
Future<void> logout() async {
|
|
state = state.copyWith(isLoading: true);
|
|
|
|
await _repository.logout();
|
|
|
|
state = const AuthState(
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Current authenticated user provider
|
|
@riverpod
|
|
User? currentUser(Ref ref) {
|
|
final authState = ref.watch(authProvider);
|
|
return authState.user;
|
|
}
|
|
|
|
/// Is authenticated provider
|
|
@riverpod
|
|
bool isAuthenticated(Ref ref) {
|
|
final authState = ref.watch(authProvider);
|
|
return authState.isAuthenticated;
|
|
}
|