import 'dart:async'; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import '../../../../core/config/api_endpoints.dart'; import '../../../../core/config/app_config.dart'; import '../../../notifications/presentation/providers/notification_providers.dart'; import '../models/auth_response.dart'; import 'tenant_provider.dart'; const _keyAccessToken = 'access_token'; const _keyRefreshToken = 'refresh_token'; final secureStorageProvider = Provider((ref) { return const FlutterSecureStorage(); }); final authStateProvider = StateNotifierProvider((ref) { return AuthNotifier(ref); }); final accessTokenProvider = FutureProvider((ref) async { final storage = ref.watch(secureStorageProvider); return storage.read(key: _keyAccessToken); }); class AuthState { final bool isAuthenticated; final bool isLoading; final String? error; final AuthUser? user; const AuthState({ this.isAuthenticated = false, this.isLoading = false, this.error, this.user, }); AuthState copyWith({ bool? isAuthenticated, bool? isLoading, String? error, AuthUser? user, }) { return AuthState( isAuthenticated: isAuthenticated ?? this.isAuthenticated, isLoading: isLoading ?? this.isLoading, error: error, user: user ?? this.user, ); } } class AuthNotifier extends StateNotifier { final Ref _ref; StreamSubscription? _notificationSubscription; AuthNotifier(this._ref) : super(const AuthState()); Future login(String email, String password) async { state = state.copyWith(isLoading: true, error: null); try { final config = _ref.read(appConfigProvider); final dio = Dio(BaseOptions(baseUrl: config.apiBaseUrl)); final response = await dio.post( ApiEndpoints.login, data: {'email': email, 'password': password}, ); final authResponse = AuthResponse.fromJson(response.data as Map); final storage = _ref.read(secureStorageProvider); await storage.write( key: _keyAccessToken, value: authResponse.accessToken); await storage.write( key: _keyRefreshToken, value: authResponse.refreshToken); state = state.copyWith( isAuthenticated: true, isLoading: false, user: authResponse.user, ); _ref.invalidate(accessTokenProvider); // Set tenant context for all subsequent API calls if (authResponse.user.tenantId != null) { _ref.read(currentTenantIdProvider.notifier).state = authResponse.user.tenantId; } // Connect notification service after login _connectNotifications(authResponse.user.tenantId); return true; } on DioException catch (e) { final message = (e.response?.data is Map) ? e.response?.data['message'] : null; state = state.copyWith( isLoading: false, error: message?.toString() ?? 'Login failed', ); return false; } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); return false; } } Future logout() async { // Disconnect notification service _notificationSubscription?.cancel(); _notificationSubscription = null; _ref.read(notificationServiceProvider).disconnect(); // Clear tenant context _ref.read(currentTenantIdProvider.notifier).state = null; final storage = _ref.read(secureStorageProvider); await storage.delete(key: _keyAccessToken); await storage.delete(key: _keyRefreshToken); _ref.invalidate(accessTokenProvider); state = const AuthState(); } Future refreshToken() async { try { final storage = _ref.read(secureStorageProvider); final refreshToken = await storage.read(key: _keyRefreshToken); if (refreshToken == null) return false; final config = _ref.read(appConfigProvider); final dio = Dio(BaseOptions(baseUrl: config.apiBaseUrl)); final response = await dio.post( ApiEndpoints.refreshToken, data: {'refreshToken': refreshToken}, ); final data = response.data as Map; await storage.write( key: _keyAccessToken, value: data['accessToken'] as String); await storage.write( key: _keyRefreshToken, value: data['refreshToken'] as String); _ref.invalidate(accessTokenProvider); return true; } catch (_) { await logout(); return false; } } void _connectNotifications(String? tenantId) { if (tenantId == null) return; try { final notificationService = _ref.read(notificationServiceProvider); final notificationList = _ref.read(notificationListProvider.notifier); notificationService.connect(tenantId); // Forward incoming notifications to the list _notificationSubscription = notificationService.notifications.listen((notification) { notificationList.add(notification); }); } catch (_) { // Non-critical — app works without push notifications } } }