TKK_E32231651/lib/global_data.dart

269 lines
8.4 KiB
Dart

// global_data.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
const String BASE_URL = 'http://192.168.100.9/kopikaocare_api';
List<Map<String, dynamic>> historyGlobal = [];
class HistoryManager {
static const String _keyHistory = 'sensor_history';
static const String _keyIntervalValue = 'interval_value';
static const String _keyIntervalUnit = 'interval_unit';
static const String _keyLastSync = 'last_sync_time';
static const String _keyCurrentRange = 'current_range';
static int intervalValue = 5;
static String intervalUnit = 'detik';
static DateTime? lastSyncTime;
static String currentRange = 'semua';
// Load history from LOCAL cache FIRST, then sync with server
static Future<void> loadHistory(
{String range = 'semua',
int limit = 1000,
bool forceRefresh = false}) async {
currentRange = range;
// First, load from local cache
await _loadHistoryLocal();
// Then, try to sync with server (if forceRefresh or cache is old)
if (forceRefresh || _shouldSyncFromServer()) {
await _syncFromServer(range, limit);
}
}
// Check if should sync from server (every 5 minutes)
static bool _shouldSyncFromServer() {
if (lastSyncTime == null) return true;
final difference = DateTime.now().difference(lastSyncTime!);
return difference.inMinutes >= 5; // Sync every 5 minutes
}
// Load from local SharedPreferences cache
static Future<void> _loadHistoryLocal() async {
final prefs = await SharedPreferences.getInstance();
// Load interval settings
intervalValue = prefs.getInt(_keyIntervalValue) ?? 5;
intervalUnit = prefs.getString(_keyIntervalUnit) ?? 'detik';
// Load last sync time
String? lastSyncStr = prefs.getString(_keyLastSync);
if (lastSyncStr != null) {
lastSyncTime = DateTime.tryParse(lastSyncStr);
}
// Load current range
currentRange = prefs.getString(_keyCurrentRange) ?? 'semua';
// Load history from local cache
String? historyString = prefs.getString(_keyHistory);
if (historyString != null && historyString.isNotEmpty) {
try {
List<dynamic> decoded = jsonDecode(historyString);
historyGlobal = decoded.map((item) {
Map<String, dynamic> map = Map<String, dynamic>.from(item);
if (map['waktu'] is String) {
map['waktu'] = DateTime.parse(map['waktu']);
}
return map;
}).toList();
print('Local cache loaded: ${historyGlobal.length} records');
} catch (e) {
print('Error loading local cache: $e');
historyGlobal = [];
}
} else {
historyGlobal = [];
print('No local cache found');
}
}
// Sync data from server with range filter
static Future<void> _syncFromServer(String range, int limit) async {
try {
print('Syncing from server with range: $range');
final response = await http.get(
Uri.parse('$BASE_URL/get_history.php?range=$range&limit=$limit'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['status'] == 'success') {
final serverData =
List<Map<String, dynamic>>.from(data['data']).map((item) {
return {
'suhu': double.parse(item['suhu'].toString()),
'kelembapan': double.parse(item['kelembapan'].toString()),
'berat': double.parse(item['berat'].toString()),
'waktu': DateTime.parse(item['waktu']),
};
}).toList();
// Replace with server data (server is source of truth for range)
historyGlobal = serverData;
await _saveHistoryLocal();
// Save current range
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyCurrentRange, range);
lastSyncTime = DateTime.now();
await _saveLastSyncTime();
print(
'Synced from server: ${historyGlobal.length} records (range: $range)');
}
}
} catch (e) {
print('Error syncing from server: $e, using cached data');
}
}
// Save to local SharedPreferences cache (PRIVATE)
static Future<void> _saveHistoryLocal() async {
final prefs = await SharedPreferences.getInstance();
List<Map<String, dynamic>> toSave = historyGlobal.map((item) {
Map<String, dynamic> copy = Map.from(item);
if (copy['waktu'] is DateTime) {
copy['waktu'] = (copy['waktu'] as DateTime).toIso8601String();
}
return copy;
}).toList();
String historyString = jsonEncode(toSave);
await prefs.setString(_keyHistory, historyString);
print('Local cache saved: ${historyGlobal.length} records');
}
// PUBLIC: Save to local SharedPreferences cache (for external use)
static Future<void> saveHistoryLocal() async {
await _saveHistoryLocal();
}
// Save last sync time
static Future<void> _saveLastSyncTime() async {
final prefs = await SharedPreferences.getInstance();
if (lastSyncTime != null) {
await prefs.setString(_keyLastSync, lastSyncTime!.toIso8601String());
}
}
// Save single data to MySQL and local cache
static Future<void> addData(Map<String, dynamic> newData) async {
try {
final response = await http.post(
Uri.parse('$BASE_URL/save_history.php'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'suhu': newData['suhu'],
'kelembapan': newData['kelembapan'],
'berat': newData['berat'],
}),
);
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
if (result['status'] == 'success') {
// Add to local cache
Map<String, dynamic> dataWithTime = {
...newData,
'waktu': DateTime.now(),
};
historyGlobal.insert(0, dataWithTime);
// Limit to 1000 data
if (historyGlobal.length > 1000) {
historyGlobal.removeLast();
}
await _saveHistoryLocal();
print('Data saved to MySQL and local cache');
}
}
} catch (e) {
print('Error saving to MySQL: $e');
}
}
// Delete selected data by index from display list
static Future<void> deleteSelectedData(List<int> indices) async {
if (indices.isEmpty) return;
// Get display data (reversed from historyGlobal)
List<Map<String, dynamic>> displayData = List.from(historyGlobal.reversed);
List<Map<String, dynamic>> toDelete = [];
for (var index in indices) {
if (index < displayData.length) {
toDelete.add(displayData[index]);
}
}
if (toDelete.isEmpty) return;
// Remove from historyGlobal
historyGlobal.removeWhere((item) {
return toDelete.any((deleteItem) =>
deleteItem['waktu'].toString() == item['waktu'].toString());
});
// Save to local cache
await _saveHistoryLocal();
// Also sync deletion to server (optional - you may need to implement server-side deletion)
// For now, we'll just update local and server will sync on next refresh
print('${toDelete.length} data deleted locally');
}
// Delete single data by its id or index
static Future<void> deleteSingleData(
Map<String, dynamic> dataToDelete) async {
historyGlobal.removeWhere((item) {
return item['waktu'].toString() == dataToDelete['waktu'].toString();
});
await _saveHistoryLocal();
print('Single data deleted');
}
// Clear all history from MySQL and local cache
static Future<void> clearHistory() async {
try {
final response = await http.post(
Uri.parse('$BASE_URL/clear_history.php'),
);
if (response.statusCode == 200) {
historyGlobal.clear();
await _saveHistoryLocal();
print('History cleared from MySQL and local cache');
}
} catch (e) {
print('Error clearing history: $e');
}
}
// Update interval settings
static Future<void> updateInterval(int value, String unit) async {
intervalValue = value;
intervalUnit = unit;
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_keyIntervalValue, value);
await prefs.setString(_keyIntervalUnit, unit);
print('Interval updated: every $value $unit');
}
// Force refresh from server
static Future<void> forceRefresh(
{String range = 'semua', int limit = 1000}) async {
await _syncFromServer(range, limit);
}
}