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'; // --- ADDED: Import for the service that manages active server configurations --- import 'package:environment_monitoring_app/services/server_config_service.dart'; // --- ADDED: Import for the new service that manages the retry queue --- import 'package:environment_monitoring_app/services/retry_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(); // --- ADDED: Instance of the ServerConfigService to set the initial URL --- final ServerConfigService _serverConfigService = ServerConfigService(); // --- ADDED: Instance of the RetryService to manage pending tasks --- final RetryService _retryService = RetryService(); // --- Session & Profile State --- String? _jwtToken; String? _userEmail; Map? _profileData; bool get isLoggedIn => _jwtToken != null; String? get userEmail => _userEmail; Map? 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>? _allUsers; List>? _tarballStations; List>? _manualStations; List>? _tarballClassifications; List>? _riverManualStations; List>? _riverTriennialStations; List>? _departments; List>? _companies; List>? _positions; List>? _airClients; List>? _airManualStations; List>? _states; List>? _appSettings; List>? _parameterLimits; List>? _apiConfigs; List>? _ftpConfigs; // --- ADDED: State variable for the list of tasks pending manual retry --- List>? _pendingRetries; // --- Getters for UI access --- List>? get allUsers => _allUsers; List>? get tarballStations => _tarballStations; List>? get manualStations => _manualStations; List>? get tarballClassifications => _tarballClassifications; List>? get riverManualStations => _riverManualStations; List>? get riverTriennialStations => _riverTriennialStations; List>? get departments => _departments; List>? get companies => _companies; List>? get positions => _positions; List>? get airClients => _airClients; List>? get airManualStations => _airManualStations; List>? get states => _states; List>? get appSettings => _appSettings; List>? get parameterLimits => _parameterLimits; List>? get apiConfigs => _apiConfigs; List>? get ftpConfigs => _ftpConfigs; // --- ADDED: Getter for the list of tasks pending manual retry --- List>? get pendingRetries => _pendingRetries; // --- SharedPreferences Keys --- 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 _loadSessionAndSyncData() async { _isLoading = true; notifyListeners(); final prefs = await SharedPreferences.getInstance(); _jwtToken = prefs.getString(tokenKey); _userEmail = prefs.getString(userEmailKey); _isFirstLogin = prefs.getBool(isFirstLoginKey) ?? true; // --- MODIFIED: Logic to insert a default URL on the first ever run --- // Check if there is an active API configuration. final activeApiConfig = await _serverConfigService.getActiveApiConfig(); if (activeApiConfig == null) { // If no config is set (which will be true on the very first launch), // we programmatically create and set a default one. debugPrint("AuthProvider: No active API config found. Setting default bootstrap URL."); final initialConfig = { 'api_config_id': 0, 'config_name': 'Default Server', 'api_url': 'https://dev14.pstw.com.my/v1', }; // Save this default config as the active one. await _serverConfigService.setActiveApiConfig(initialConfig); } // --- END OF MODIFICATION --- // MODIFIED: Switched to getting a string for the ISO8601 timestamp final lastSyncString = prefs.getString(lastSyncTimestampKey); if (lastSyncString != null) { _lastSyncTimestamp = DateTime.parse(lastSyncString); } // 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 using the delta-sync strategy. Future syncAllData({bool forceRefresh = false}) async { final connectivityResult = await Connectivity().checkConnectivity(); if (connectivityResult == ConnectivityResult.none) { debugPrint("AuthProvider: Device is OFFLINE. Skipping sync."); return; } debugPrint("AuthProvider: Device is ONLINE. Starting delta sync."); final prefs = await SharedPreferences.getInstance(); // If 'forceRefresh' is true, sync all data by passing a null timestamp. final String? lastSync = forceRefresh ? null : prefs.getString(lastSyncTimestampKey); // Record the time BEFORE the sync starts. This will be our new timestamp on success. // Use UTC for consistency across timezones. final newSyncTimestamp = DateTime.now().toUtc().toIso8601String(); final result = await _apiService.syncAllData(lastSyncTimestamp: lastSync); if (result['success']) { debugPrint("AuthProvider: Delta sync successful. Updating last sync timestamp."); // On success, save the new timestamp for the next run. await prefs.setString(lastSyncTimestampKey, newSyncTimestamp); _lastSyncTimestamp = DateTime.parse(newSyncTimestamp); // --- ADDED: After the first successful sync, set isFirstLogin to false --- if (_isFirstLogin) { await setIsFirstLogin(false); debugPrint("AuthProvider: First successful sync complete. isFirstLogin flag set to false."); } // --- END --- // After updating the DB, reload data from the cache into memory to update the UI. await _loadDataFromCache(); notifyListeners(); } else { debugPrint("AuthProvider: Delta sync failed. Timestamp not updated."); } } /// A dedicated method to refresh only the profile. Future refreshProfile() async { final result = await _apiService.refreshProfile(); if (result['success']) { _profileData = result['data']; // Persist the updated profile data await _dbHelper.saveProfile(_profileData!); final prefs = await SharedPreferences.getInstance(); await prefs.setString(profileDataKey, jsonEncode(_profileData)); notifyListeners(); } } /// Loads all master data from the local cache using DatabaseHelper. Future _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(); _states = await _dbHelper.loadStates(); // ADDED: Load new data types from the local database _appSettings = await _dbHelper.loadAppSettings(); _parameterLimits = await _dbHelper.loadParameterLimits(); _apiConfigs = await _dbHelper.loadApiConfigs(); _ftpConfigs = await _dbHelper.loadFtpConfigs(); // --- ADDED: Load pending retry tasks from the database --- _pendingRetries = await _retryService.getPendingTasks(); debugPrint("AuthProvider: All master data loaded from local DB cache."); } // --- ADDED: A public method to allow the UI to refresh the pending tasks list --- /// Refreshes the list of pending retry tasks from the local database. Future refreshPendingTasks() async { _pendingRetries = await _retryService.getPendingTasks(); notifyListeners(); } // --- Methods for UI interaction --- /// Handles the login process, saving session data and triggering a full data sync. Future login(String token, Map 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.'); // Perform a full refresh on login to ensure data is pristine. await syncAllData(forceRefresh: true); } Future setProfileData(Map 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 setIsFirstLogin(bool value) async { _isFirstLogin = value; final prefs = await SharedPreferences.getInstance(); await prefs.setBool(isFirstLoginKey, value); notifyListeners(); } Future 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; _states = null; // ADDED: Clear new data on logout _appSettings = null; _parameterLimits = null; _apiConfigs = null; _ftpConfigs = null; // --- ADDED: Clear pending retry tasks on logout --- _pendingRetries = null; final prefs = await SharedPreferences.getInstance(); // MODIFIED: Removed keys individually for safer logout await prefs.remove(tokenKey); await prefs.remove(userEmailKey); await prefs.remove(profileDataKey); await prefs.remove(lastSyncTimestampKey); await prefs.setBool(isFirstLoginKey, true); debugPrint('AuthProvider: All session and cached data cleared.'); notifyListeners(); } Future> resetPassword(String email) { return _apiService.post('auth/forgot-password', {'email': email}); } }