import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import 'package:environment_monitoring_app/auth_provider.dart'; import 'package:environment_monitoring_app/services/settings_service.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { final SettingsService _settingsService = SettingsService(); bool _isSyncingData = false; bool _isSyncingSettings = false; String _inSituChatId = 'Loading...'; String _tarballChatId = 'Loading...'; final TextEditingController _tarballSearchController = TextEditingController(); String _tarballSearchQuery = ''; final TextEditingController _manualSearchController = TextEditingController(); String _manualSearchQuery = ''; final TextEditingController _riverManualSearchController = TextEditingController(); String _riverManualSearchQuery = ''; final TextEditingController _riverTriennialSearchController = TextEditingController(); String _riverTriennialSearchQuery = ''; @override void initState() { super.initState(); _loadCurrentSettings(); _tarballSearchController.addListener(_onTarballSearchChanged); _manualSearchController.addListener(_onManualSearchChanged); _riverManualSearchController.addListener(_onRiverManualSearchChanged); _riverTriennialSearchController.addListener(_onRiverTriennialSearchChanged); } @override void dispose() { _tarballSearchController.dispose(); _manualSearchController.dispose(); _riverManualSearchController.dispose(); _riverTriennialSearchController.dispose(); super.dispose(); } Future _loadCurrentSettings() async { final inSituId = await _settingsService.getInSituChatId(); final tarballId = await _settingsService.getTarballChatId(); if (mounted) { setState(() { _inSituChatId = inSituId.isNotEmpty ? inSituId : 'Not Set'; _tarballChatId = tarballId.isNotEmpty ? tarballId : 'Not Set'; }); } } void _onTarballSearchChanged() { setState(() { _tarballSearchQuery = _tarballSearchController.text; }); } void _onManualSearchChanged() { setState(() { _manualSearchQuery = _manualSearchController.text; }); } void _onRiverManualSearchChanged() { setState(() { _riverManualSearchQuery = _riverManualSearchController.text; }); } void _onRiverTriennialSearchChanged() { setState(() { _riverTriennialSearchQuery = _riverTriennialSearchController.text; }); } // --- FIXED: This method now uses try/catch to handle success and failure --- Future _manualDataSync() async { if (_isSyncingData) return; setState(() => _isSyncingData = true); final auth = Provider.of(context, listen: false); try { // This function doesn't return a value, so we don't assign it to a variable. await auth.syncAllData(forceRefresh: true); // If no error was thrown, the sync was successful. if (mounted) { _showSnackBar('Data synced successfully.', isError: false); } } catch (e) { // If an error was thrown during the sync, we catch it here. if (mounted) { _showSnackBar('Data sync failed. Please check your connection.', isError: true); } } finally { // This will run whether the sync succeeded or failed. if (mounted) { setState(() => _isSyncingData = false); } } } Future _manualSettingsSync() async { if (_isSyncingSettings) return; setState(() => _isSyncingSettings = true); final success = await _settingsService.syncFromServer(); if (mounted) { final message = success ? 'Telegram settings synced successfully.' : 'Failed to sync settings.'; _showSnackBar(message, isError: !success); if (success) { await _loadCurrentSettings(); } setState(() => _isSyncingSettings = false); } } void _showSnackBar(String message, {bool isError = false}) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? Theme.of(context).colorScheme.error : Colors.green, ), ); } } @override Widget build(BuildContext context) { final auth = Provider.of(context); final lastSync = auth.lastSyncTimestamp; // Filtering logic is unchanged final filteredTarballStations = auth.tarballStations?.where((station) { final stationName = station['tbl_station_name']?.toLowerCase() ?? ''; final stationCode = station['tbl_station_code']?.toLowerCase() ?? ''; final query = _tarballSearchQuery.toLowerCase(); return stationName.contains(query) || stationCode.contains(query); }).toList(); final filteredManualStations = auth.manualStations?.where((station) { final stationName = station['man_station_name']?.toLowerCase() ?? ''; final stationCode = station['man_station_code']?.toLowerCase() ?? ''; final query = _manualSearchQuery.toLowerCase(); return stationName.contains(query) || stationCode.contains(query); }).toList(); final filteredRiverManualStations = auth.riverManualStations?.where((station) { final riverName = station['sampling_river']?.toLowerCase() ?? ''; final stationCode = station['sampling_station_code']?.toLowerCase() ?? ''; final basinName = station['sampling_basin']?.toLowerCase() ?? ''; final query = _riverManualSearchQuery.toLowerCase(); return riverName.contains(query) || stationCode.contains(query) || basinName.contains(query); }).toList(); final filteredRiverTriennialStations = auth.riverTriennialStations?.where((station) { final riverName = station['triennial_river']?.toLowerCase() ?? ''; final stationCode = station['triennial_station_code']?.toLowerCase() ?? ''; final basinName = station['triennial_basin']?.toLowerCase() ?? ''; final query = _riverTriennialSearchQuery.toLowerCase(); return riverName.contains(query) || stationCode.contains(query) || basinName.contains(query); }).toList(); return Scaffold( appBar: AppBar( title: const Text("Settings"), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Synchronization", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text("Last Data Sync:", style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 4), Text(lastSync != null ? DateFormat('yyyy-MM-dd HH:mm:ss').format(lastSync) : 'Never', style: Theme.of(context).textTheme.bodyLarge), const SizedBox(height: 16), ElevatedButton.icon( onPressed: _isSyncingData ? null : _manualDataSync, icon: _isSyncingData ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.cloud_sync), label: Text(_isSyncingData ? 'Syncing Data...' : 'Sync App Data'), style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 50)), ), ], ), ), ), const SizedBox(height: 32), Text("Telegram Alert Settings", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ListTile( contentPadding: EdgeInsets.zero, leading: const Icon(Icons.telegram), title: const Text('Marine In-Situ Chat ID'), subtitle: Text(_inSituChatId), ), ListTile( contentPadding: EdgeInsets.zero, leading: const Icon(Icons.telegram), title: const Text('Marine Tarball Chat ID'), subtitle: Text(_tarballChatId), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: _isSyncingSettings ? null : _manualSettingsSync, icon: _isSyncingSettings ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.settings_backup_restore), label: Text(_isSyncingSettings ? 'Syncing Settings...' : 'Sync Telegram Settings'), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.secondary, minimumSize: const Size(double.infinity, 50), ), ), ], ), ), ), const SizedBox(height: 32), Text("Marine Tarball Stations (${filteredTarballStations?.length ?? 0} found)", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField(controller: _tarballSearchController, decoration: InputDecoration(labelText: 'Search Tarball Stations', hintText: 'Search by name or code', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), suffixIcon: _tarballSearchController.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear), onPressed: () => _tarballSearchController.clear()) : null)), const SizedBox(height: 16), _buildStationList(filteredTarballStations, 'No matching tarball stations found.', 'No tarball stations available. Sync to download.', (station) => ListTile(title: Text(station['tbl_station_name'] ?? 'N/A'), subtitle: Text('Code: ${station['tbl_station_code'] ?? 'N/A'}'))), ], ), ), ), const SizedBox(height: 32), Text("Marine Manual Stations (${filteredManualStations?.length ?? 0} found)", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField(controller: _manualSearchController, decoration: InputDecoration(labelText: 'Search Manual Stations', hintText: 'Search by name or code', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), suffixIcon: _manualSearchController.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear), onPressed: () => _manualSearchController.clear()) : null)), const SizedBox(height: 16), _buildStationList(filteredManualStations, 'No matching manual stations found.', 'No manual stations available. Sync to download.', (station) => ListTile(title: Text(station['man_station_name'] ?? 'N/A'), subtitle: Text('Code: ${station['man_station_code'] ?? 'N/A'}'))), ], ), ), ), const SizedBox(height: 32), Text("River Manual Stations (${filteredRiverManualStations?.length ?? 0} found)", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField(controller: _riverManualSearchController, decoration: InputDecoration(labelText: 'Search River Manual Stations', hintText: 'Search by river, basin, or code', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), suffixIcon: _riverManualSearchController.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear), onPressed: () => _riverManualSearchController.clear()) : null)), const SizedBox(height: 16), _buildStationList(filteredRiverManualStations, 'No matching river manual stations found.', 'No river manual stations available. Sync to download.', (station) => ListTile(title: Text(station['sampling_river'] ?? 'N/A'), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text('Code: ${station['sampling_station_code'] ?? 'N/A'}'), Text('Basin: ${station['sampling_basin'] ?? 'N/A'}'), Text('State: ${station['state_name'] ?? 'N/A'}')]))), ], ), ), ), const SizedBox(height: 32), Text("River Triennial Stations (${filteredRiverTriennialStations?.length ?? 0} found)", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField(controller: _riverTriennialSearchController, decoration: InputDecoration(labelText: 'Search River Triennial Stations', hintText: 'Search by river, basin, or code', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), suffixIcon: _riverTriennialSearchController.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear), onPressed: () => _riverTriennialSearchController.clear()) : null)), const SizedBox(height: 16), _buildStationList(filteredRiverTriennialStations, 'No matching river triennial stations found.', 'No river triennial stations available. Sync to download.', (station) => ListTile(title: Text(station['triennial_river'] ?? 'N/A'), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text('Code: ${station['triennial_station_code'] ?? 'N/A'}'), Text('Basin: ${station['triennial_basin'] ?? 'N/A'}'), Text('State: ${station['state_name'] ?? 'N/A'}')]))), ], ), ), ), const SizedBox(height: 32), Text("General Settings", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), Card( margin: EdgeInsets.zero, child: Column( children: [ ListTile(leading: const Icon(Icons.info_outline), title: const Text('App Version'), subtitle: const Text('1.0.0')), ListTile(leading: const Icon(Icons.privacy_tip_outlined), title: const Text('Privacy Policy'), onTap: () {}), ], ), ), ], ), ), ); } Widget _buildStationList(List>? stations, String noMatchText, String noDataText, Widget Function(Map) itemBuilder) { if (stations == null || stations.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Text( _isSyncingData ? 'Loading...' : (stations == null ? noDataText : noMatchText), ), ), ); } return SizedBox( height: 250, child: ListView.builder( itemCount: stations.length, itemBuilder: (context, index) { final station = stations[index]; return itemBuilder(station); }, ), ); } }