import 'dart:async'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../core/api/api_client.dart'; import '../domain/app_user.dart'; part 'auth_repository.g.dart'; /// Manages auth state backed by the PHP/MySQL API. /// /// Because the backend has no push mechanism, auth state is held in memory and /// exposed via a [StreamController]. Sign-in and registration update the stream /// immediately; the token is persisted in [FlutterSecureStorage] via [ApiClient]. class AuthRepository { AuthRepository(this._api) { _init(); } final ApiClient _api; final _controller = StreamController.broadcast(); Stream authStateChanges() => _controller.stream; AppUser? get currentUser => _currentUser; AppUser? _currentUser; Future _init() async { final token = await _api.token; if (token == null) { _emit(null); return; } try { final data = await _api.get('/auth/me.php'); final user = _mapUser(data); _emit(user); } catch (_) { await _api.clearToken(); _emit(null); } } Future signInWithEmail({ required String email, required String password, }) async { final data = await _api.post( '/auth/login.php', {'email': email.trim(), 'password': password}, auth: false, ); await _api.saveToken(data['token'] as String); final user = _mapUser(data['user'] as Map); _emit(user); return user; } Future registerWithEmail({ required String email, required String password, String? displayName, }) async { final data = await _api.post( '/auth/register.php', { 'email': email.trim(), 'password': password, 'display_name': displayName?.trim() ?? '', }, auth: false, ); await _api.saveToken(data['token'] as String); final user = _mapUser(data['user'] as Map); _emit(user); return user; } Future signOut() async { await _api.clearToken(); _emit(null); } void _emit(AppUser? user) { _currentUser = user; _controller.add(user); } AppUser? _mapUser(Map data) { final id = data['id'] as String?; if (id == null || id.isEmpty) return null; return AppUser( uid: id, email: (data['email'] as String?) ?? '', displayName: data['display_name'] as String?, photoUrl: data['photo_url'] as String?, ); } void dispose() => _controller.close(); } @Riverpod(keepAlive: true) AuthRepository authRepository(AuthRepositoryRef ref) { final repo = AuthRepository(ref.watch(apiClientProvider)); ref.onDispose(repo.dispose); return repo; } @Riverpod(keepAlive: true) Stream authStateChanges(AuthStateChangesRef ref) { return ref.watch(authRepositoryProvider).authStateChanges(); }