Refactor: Integrate backend API and normalize data

This commit integrates the backend API for fetching and updating report data. It also includes a normalization function to handle data consistency between the API and local storage.

Co-authored-by: anthonymuncher <anthonymuncher@gmail.com>
This commit is contained in:
Cursor Agent
2025-09-26 10:27:39 +00:00
parent 1637e013c5
commit 46dea3304f
39 changed files with 29186 additions and 23 deletions

View File

@@ -1,16 +1,29 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';
import '../models/report.dart';
import 'api_service.dart';
/// Service for persisting reports and managing local storage
class StorageService {
static const String _reportsKey = 'reports_v1';
/// Get all reports from storage
/// Get all reports from storage (API first, fallback to local)
static Future<List<Report>> getReports() async {
try {
// Try API first
final apiReports = await ApiService.getReports();
if (apiReports.isNotEmpty) {
return apiReports;
}
} catch (e) {
print('API not available, falling back to local storage: $e');
}
// Fallback to local storage
try {
final prefs = await SharedPreferences.getInstance();
final reportsJson = prefs.getString(_reportsKey);
@@ -27,8 +40,31 @@ class StorageService {
}
}
/// Save a single report to storage
/// Save a single report to storage (API first, fallback to local)
static Future<bool> saveReport(Report report) async {
try {
// Try API first - convert Report to API format
final imageBytes = report.photoPath != null
? await _getImageBytes(report)
: report.base64Photo != null
? base64.decode(report.base64Photo!)
: null;
if (imageBytes != null) {
await ApiService.submitReport(
latitude: report.location.lat,
longitude: report.location.lng,
description: report.notes ?? '',
imageBytes: imageBytes,
imageName: '${report.id}.jpg',
);
return true;
}
} catch (e) {
print('API not available, falling back to local storage: $e');
}
// Fallback to local storage
try {
final reports = await getReports();
final existingIndex = reports.indexWhere((r) => r.id == report.id);
@@ -46,8 +82,21 @@ class StorageService {
}
}
/// Delete a report from storage
/// Delete a report from storage (API first, fallback to local)
static Future<bool> deleteReport(String reportId) async {
try {
// Try API first (note: API doesn't have delete endpoint, so this will always fallback)
final apiReport = await ApiService.getReportById(reportId);
if (apiReport != null) {
// For now, the API doesn't have a delete endpoint, so we can't delete from API
// This would need to be added to the backend
print('API delete not available, keeping local copy');
}
} catch (e) {
print('API not available: $e');
}
// Fallback to local storage
try {
final reports = await getReports();
final updatedReports = reports.where((r) => r.id != reportId).toList();
@@ -66,9 +115,10 @@ class StorageService {
}
}
/// Clear all reports from storage
/// Clear all reports from storage (local only, API doesn't have clear endpoint)
static Future<bool> clearAllReports() async {
try {
// Note: API doesn't have a clear all endpoint, so we only clear local storage
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_reportsKey);
@@ -177,6 +227,30 @@ class StorageService {
}
}
/// Get image bytes for API submission
static Future<Uint8List?> _getImageBytes(Report report) async {
if (report.photoPath != null) {
try {
final file = File(report.photoPath!);
if (await file.exists()) {
return await file.readAsBytes();
}
} catch (e) {
print('Error reading image file: $e');
}
}
if (report.base64Photo != null) {
try {
return base64.decode(report.base64Photo!);
} catch (e) {
print('Error decoding base64 image: $e');
}
}
return null;
}
/// Get storage statistics
static Future<StorageStats> getStorageStats() async {
try {