248 lines
10 KiB
Dart
248 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
import 'dart:convert';
|
|
|
|
import 'package:environment_monitoring_app/services/api_service.dart';
|
|
|
|
/// A comprehensive provider to manage user authentication, session state,
|
|
/// and cached master data for offline use.
|
|
class AuthProvider with ChangeNotifier {
|
|
final ApiService _apiService = ApiService();
|
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
|
|
|
// --- Session & Profile State ---
|
|
String? _jwtToken;
|
|
String? _userEmail;
|
|
Map<String, dynamic>? _profileData;
|
|
bool get isLoggedIn => _jwtToken != null;
|
|
String? get userEmail => _userEmail;
|
|
Map<String, dynamic>? get profileData => _profileData;
|
|
|
|
// --- App State ---
|
|
bool _isLoading = true;
|
|
bool _isFirstLogin = true;
|
|
DateTime? _lastSyncTimestamp;
|
|
bool get isLoading => _isLoading;
|
|
bool get isFirstLogin => _isFirstLogin;
|
|
DateTime? get lastSyncTimestamp => _lastSyncTimestamp;
|
|
|
|
// --- Cached Master Data ---
|
|
List<Map<String, dynamic>>? _allUsers;
|
|
List<Map<String, dynamic>>? _tarballStations;
|
|
List<Map<String, dynamic>>? _manualStations;
|
|
List<Map<String, dynamic>>? _tarballClassifications;
|
|
List<Map<String, dynamic>>? _riverManualStations;
|
|
List<Map<String, dynamic>>? _riverTriennialStations;
|
|
List<Map<String, dynamic>>? _departments;
|
|
List<Map<String, dynamic>>? _companies;
|
|
List<Map<String, dynamic>>? _positions;
|
|
List<Map<String, dynamic>>? _airClients;
|
|
List<Map<String, dynamic>>? _airManualStations;
|
|
// --- ADDED FOR STATE LIST ---
|
|
List<Map<String, dynamic>>? _states;
|
|
|
|
// --- Getters for UI access ---
|
|
List<Map<String, dynamic>>? get allUsers => _allUsers;
|
|
List<Map<String, dynamic>>? get tarballStations => _tarballStations;
|
|
List<Map<String, dynamic>>? get manualStations => _manualStations;
|
|
List<Map<String, dynamic>>? get tarballClassifications => _tarballClassifications;
|
|
List<Map<String, dynamic>>? get riverManualStations => _riverManualStations;
|
|
List<Map<String, dynamic>>? get riverTriennialStations => _riverTriennialStations;
|
|
List<Map<String, dynamic>>? get departments => _departments;
|
|
List<Map<String, dynamic>>? get companies => _companies;
|
|
List<Map<String, dynamic>>? get positions => _positions;
|
|
List<Map<String, dynamic>>? get airClients => _airClients;
|
|
List<Map<String, dynamic>>? get airManualStations => _airManualStations;
|
|
// --- ADDED FOR STATE LIST ---
|
|
List<Map<String, dynamic>>? get states => _states;
|
|
|
|
// --- SharedPreferences Keys (made public for BaseApiService) ---
|
|
static const String tokenKey = 'jwt_token';
|
|
static const String userEmailKey = 'user_email';
|
|
static const String profileDataKey = 'user_profile_data';
|
|
static const String lastSyncTimestampKey = 'last_sync_timestamp';
|
|
static const String isFirstLoginKey = 'is_first_login';
|
|
|
|
AuthProvider() {
|
|
debugPrint('AuthProvider: Initializing...');
|
|
_loadSessionAndSyncData();
|
|
}
|
|
|
|
/// Loads the user session from storage and then triggers a data sync.
|
|
Future<void> _loadSessionAndSyncData() async {
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
_jwtToken = prefs.getString(tokenKey);
|
|
_userEmail = prefs.getString(userEmailKey);
|
|
_isFirstLogin = prefs.getBool(isFirstLoginKey) ?? true;
|
|
final lastSyncMillis = prefs.getInt(lastSyncTimestampKey);
|
|
if (lastSyncMillis != null) {
|
|
_lastSyncTimestamp = DateTime.fromMillisecondsSinceEpoch(lastSyncMillis);
|
|
}
|
|
|
|
// Always load from local DB first for instant startup
|
|
await _loadDataFromCache();
|
|
|
|
if (_jwtToken != null) {
|
|
debugPrint('AuthProvider: Session loaded. Triggering online sync.');
|
|
// Don't await here to allow the UI to build instantly with cached data
|
|
syncAllData();
|
|
} else {
|
|
debugPrint('AuthProvider: No active session. App is in offline mode.');
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// The main function to sync all app data. It checks for an internet connection
|
|
/// and fetches from the server if available, otherwise it relies on the local cache.
|
|
Future<void> syncAllData({bool forceRefresh = false}) async {
|
|
final connectivityResult = await Connectivity().checkConnectivity();
|
|
if (connectivityResult != ConnectivityResult.none) {
|
|
debugPrint("AuthProvider: Device is ONLINE. Fetching fresh data from server.");
|
|
await _fetchDataFromServer();
|
|
} else {
|
|
debugPrint("AuthProvider: Device is OFFLINE. Data is already loaded from cache.");
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
/// A dedicated method to refresh only the profile.
|
|
Future<void> refreshProfile() async {
|
|
final result = await _apiService.refreshProfile();
|
|
if (result['success']) {
|
|
// Update the profile data in the provider state
|
|
_profileData = result['data'];
|
|
// Persist the updated profile data in SharedPreferences
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(profileDataKey, jsonEncode(_profileData));
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Fetches all master data from the server and caches it locally.
|
|
Future<void> _fetchDataFromServer() async {
|
|
final result = await _apiService.syncAllData();
|
|
if (result['success']) {
|
|
final data = result['data'];
|
|
_profileData = data['profile'];
|
|
_allUsers = data['allUsers'] != null ? List<Map<String, dynamic>>.from(data['allUsers']) : null;
|
|
_tarballStations = data['tarballStations'] != null ? List<Map<String, dynamic>>.from(data['tarballStations']) : null;
|
|
_manualStations = data['manualStations'] != null ? List<Map<String, dynamic>>.from(data['manualStations']) : null;
|
|
_tarballClassifications = data['tarballClassifications'] != null ? List<Map<String, dynamic>>.from(data['tarballClassifications']) : null;
|
|
_riverManualStations = data['riverManualStations'] != null ? List<Map<String, dynamic>>.from(data['riverManualStations']) : null;
|
|
_riverTriennialStations = data['riverTriennialStations'] != null ? List<Map<String, dynamic>>.from(data['riverTriennialStations']) : null;
|
|
_departments = data['departments'] != null ? List<Map<String, dynamic>>.from(data['departments']) : null;
|
|
_companies = data['companies'] != null ? List<Map<String, dynamic>>.from(data['companies']) : null;
|
|
_positions = data['positions'] != null ? List<Map<String, dynamic>>.from(data['positions']) : null;
|
|
_airClients = data['airClients'] != null ? List<Map<String, dynamic>>.from(data['airClients']) : null;
|
|
_airManualStations = data['airManualStations'] != null ? List<Map<String, dynamic>>.from(data['airManualStations']) : null;
|
|
|
|
// --- ADDED FOR STATE LIST ---
|
|
// Note: `syncAllData` in ApiService must be updated to fetch 'states'
|
|
_states = data['states'] != null ? List<Map<String, dynamic>>.from(data['states']) : null;
|
|
|
|
await setLastSyncTimestamp(DateTime.now());
|
|
}
|
|
}
|
|
|
|
/// Loads all master data from the local cache using DatabaseHelper.
|
|
Future<void> _loadDataFromCache() async {
|
|
_profileData = await _dbHelper.loadProfile();
|
|
_allUsers = await _dbHelper.loadUsers();
|
|
_tarballStations = await _dbHelper.loadTarballStations();
|
|
_manualStations = await _dbHelper.loadManualStations();
|
|
_tarballClassifications = await _dbHelper.loadTarballClassifications();
|
|
_riverManualStations = await _dbHelper.loadRiverManualStations();
|
|
_riverTriennialStations = await _dbHelper.loadRiverTriennialStations();
|
|
_departments = await _dbHelper.loadDepartments();
|
|
_companies = await _dbHelper.loadCompanies();
|
|
_positions = await _dbHelper.loadPositions();
|
|
_airClients = await _dbHelper.loadAirClients();
|
|
_airManualStations = await _dbHelper.loadAirManualStations();
|
|
|
|
// --- ADDED FOR STATE LIST ---
|
|
// Note: `loadStates()` must be added to your DatabaseHelper class
|
|
_states = await _dbHelper.loadStates();
|
|
|
|
debugPrint("AuthProvider: All master data loaded from local DB cache.");
|
|
}
|
|
|
|
// --- Methods for UI interaction ---
|
|
|
|
/// Handles the login process, saving session data and triggering a full data sync.
|
|
Future<void> login(String token, Map<String, dynamic> profile) async {
|
|
_jwtToken = token;
|
|
_userEmail = profile['email'];
|
|
_profileData = profile;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(tokenKey, token);
|
|
await prefs.setString(userEmailKey, _userEmail!);
|
|
await prefs.setString(profileDataKey, jsonEncode(profile));
|
|
await _dbHelper.saveProfile(profile);
|
|
|
|
debugPrint('AuthProvider: Login successful. Session and profile persisted.');
|
|
await syncAllData(forceRefresh: true);
|
|
}
|
|
|
|
Future<void> setProfileData(Map<String, dynamic> data) async {
|
|
_profileData = data;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(profileDataKey, jsonEncode(data));
|
|
await _dbHelper.saveProfile(data); // Also save to local DB
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setLastSyncTimestamp(DateTime timestamp) async {
|
|
_lastSyncTimestamp = timestamp;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setInt(lastSyncTimestampKey, timestamp.millisecondsSinceEpoch);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setIsFirstLogin(bool value) async {
|
|
_isFirstLogin = value;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(isFirstLoginKey, value);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
debugPrint('AuthProvider: Initiating logout...');
|
|
_jwtToken = null;
|
|
_userEmail = null;
|
|
_profileData = null;
|
|
_lastSyncTimestamp = null;
|
|
_isFirstLogin = true;
|
|
_allUsers = null;
|
|
_tarballStations = null;
|
|
_manualStations = null;
|
|
_tarballClassifications = null;
|
|
_riverManualStations = null;
|
|
_riverTriennialStations = null;
|
|
_departments = null;
|
|
_companies = null;
|
|
_positions = null;
|
|
_airClients = null;
|
|
_airManualStations = null;
|
|
|
|
// --- ADDED FOR STATE LIST ---
|
|
_states = null;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.clear();
|
|
await prefs.setBool(isFirstLoginKey, true);
|
|
|
|
debugPrint('AuthProvider: All session and cached data cleared.');
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<Map<String, dynamic>> resetPassword(String email) {
|
|
return _apiService.post('auth/forgot-password', {'email': email});
|
|
}
|
|
} |