368 lines
13 KiB
Dart
368 lines
13 KiB
Dart
// lib/services/local_storage_service.dart
|
|
|
|
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import '../models/tarball_data.dart';
|
|
import '../models/in_situ_sampling_data.dart';
|
|
// ADDED: Import the river-specific data model
|
|
import '../models/river_in_situ_sampling_data.dart';
|
|
|
|
/// A comprehensive service for handling all local data storage for offline submissions.
|
|
class LocalStorageService {
|
|
|
|
// =======================================================================
|
|
// Part 1: Public Storage Setup
|
|
// =======================================================================
|
|
|
|
/// Checks for and requests necessary storage permissions for public storage.
|
|
Future<bool> _requestPermissions() async {
|
|
var status = await Permission.manageExternalStorage.request();
|
|
return status.isGranted;
|
|
}
|
|
|
|
/// Gets the public external storage directory and creates the base MMSV4 folder.
|
|
Future<Directory?> _getPublicMMSV4Directory() async {
|
|
if (await _requestPermissions()) {
|
|
final Directory? externalDir = await getExternalStorageDirectory();
|
|
if (externalDir != null) {
|
|
// Navigates up from the app-specific folder to the public root
|
|
final publicRootPath = externalDir.path.split('/Android/')[0];
|
|
final mmsv4Dir = Directory(p.join(publicRootPath, 'MMSV4'));
|
|
if (!await mmsv4Dir.exists()) {
|
|
await mmsv4Dir.create(recursive: true);
|
|
}
|
|
return mmsv4Dir;
|
|
}
|
|
}
|
|
debugPrint("LocalStorageService: Manage External Storage permission was not granted.");
|
|
return null;
|
|
}
|
|
|
|
// =======================================================================
|
|
// Part 2: Tarball Specific Methods
|
|
// =======================================================================
|
|
|
|
/// Gets the base directory for storing tarball sampling data logs.
|
|
Future<Directory?> _getTarballBaseDir() async {
|
|
final mmsv4Dir = await _getPublicMMSV4Directory();
|
|
if (mmsv4Dir == null) return null;
|
|
|
|
final tarballDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_tarball_sampling'));
|
|
if (!await tarballDir.exists()) {
|
|
await tarballDir.create(recursive: true);
|
|
}
|
|
return tarballDir;
|
|
}
|
|
|
|
/// Saves a single tarball sampling record to a unique folder in public storage.
|
|
Future<String?> saveTarballSamplingData(TarballSamplingData data) async {
|
|
final baseDir = await _getTarballBaseDir();
|
|
if (baseDir == null) {
|
|
debugPrint("Could not get public storage directory. Check permissions.");
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
final stationCode = data.selectedStation?['tbl_station_code'] ?? 'UNKNOWN_STATION';
|
|
final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
|
|
final eventFolderName = "${stationCode}_$timestamp";
|
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
|
|
|
if (!await eventDir.exists()) {
|
|
await eventDir.create(recursive: true);
|
|
}
|
|
|
|
final Map<String, dynamic> jsonData = { ...data.toFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };
|
|
|
|
jsonData['selectedStation'] = data.selectedStation;
|
|
|
|
final imageFiles = data.toImageFiles();
|
|
|
|
for (var entry in imageFiles.entries) {
|
|
final File? imageFile = entry.value;
|
|
if (imageFile != null) {
|
|
final String originalFileName = p.basename(imageFile.path);
|
|
final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
|
|
jsonData[entry.key] = newFile.path;
|
|
}
|
|
}
|
|
|
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
|
debugPrint("Tarball log saved to: ${jsonFile.path}");
|
|
|
|
return eventDir.path;
|
|
|
|
} catch (e) {
|
|
debugPrint("Error saving tarball log to local storage: $e");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Retrieves all saved tarball submission logs from public storage.
|
|
Future<List<Map<String, dynamic>>> getAllTarballLogs() async {
|
|
final baseDir = await _getTarballBaseDir();
|
|
if (baseDir == null || !await baseDir.exists()) return [];
|
|
|
|
try {
|
|
final List<Map<String, dynamic>> logs = [];
|
|
final entities = baseDir.listSync();
|
|
|
|
for (var entity in entities) {
|
|
if (entity is Directory) {
|
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
final content = await jsonFile.readAsString();
|
|
final data = jsonDecode(content) as Map<String, dynamic>;
|
|
data['logDirectory'] = entity.path; // Add directory path for resubmission/update
|
|
logs.add(data);
|
|
}
|
|
}
|
|
}
|
|
return logs;
|
|
} catch (e) {
|
|
debugPrint("Error getting all tarball logs: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Updates an existing log file with new submission status.
|
|
Future<void> updateTarballLog(Map<String, dynamic> updatedLogData) async {
|
|
final logDir = updatedLogData['logDirectory'];
|
|
if (logDir == null) {
|
|
debugPrint("Cannot update log: logDirectory key is missing.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
updatedLogData.remove('isResubmitting');
|
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
|
debugPrint("Log updated successfully at: ${jsonFile.path}");
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error updating tarball log: $e");
|
|
}
|
|
}
|
|
|
|
|
|
// =======================================================================
|
|
// Part 3: Marine In-Situ Specific Methods
|
|
// =======================================================================
|
|
|
|
/// Gets the base directory for storing marine in-situ sampling data logs.
|
|
Future<Directory?> _getInSituBaseDir() async {
|
|
final mmsv4Dir = await _getPublicMMSV4Directory();
|
|
if (mmsv4Dir == null) return null;
|
|
|
|
final inSituDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_in_situ_sampling'));
|
|
if (!await inSituDir.exists()) {
|
|
await inSituDir.create(recursive: true);
|
|
}
|
|
return inSituDir;
|
|
}
|
|
|
|
/// Saves a single marine in-situ sampling record to a unique folder in public storage.
|
|
Future<String?> saveInSituSamplingData(InSituSamplingData data) async {
|
|
final baseDir = await _getInSituBaseDir();
|
|
if (baseDir == null) {
|
|
debugPrint("Could not get public storage directory for In-Situ. Check permissions.");
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
final stationCode = data.selectedStation?['man_station_code'] ?? 'UNKNOWN_STATION';
|
|
final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
|
|
final eventFolderName = "${stationCode}_$timestamp";
|
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
|
|
|
if (!await eventDir.exists()) {
|
|
await eventDir.create(recursive: true);
|
|
}
|
|
|
|
final Map<String, dynamic> jsonData = { ...data.toApiFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };
|
|
|
|
jsonData['selectedStation'] = data.selectedStation;
|
|
|
|
final imageFiles = data.toApiImageFiles();
|
|
for (var entry in imageFiles.entries) {
|
|
final File? imageFile = entry.value;
|
|
if (imageFile != null) {
|
|
final String originalFileName = p.basename(imageFile.path);
|
|
final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
|
|
jsonData[entry.key] = newFile.path;
|
|
}
|
|
}
|
|
|
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
|
debugPrint("In-Situ log saved to: ${jsonFile.path}");
|
|
|
|
return eventDir.path;
|
|
|
|
} catch (e) {
|
|
debugPrint("Error saving In-Situ log to local storage: $e");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Retrieves all saved marine in-situ submission logs from public storage.
|
|
Future<List<Map<String, dynamic>>> getAllInSituLogs() async {
|
|
final baseDir = await _getInSituBaseDir();
|
|
if (baseDir == null || !await baseDir.exists()) return [];
|
|
|
|
try {
|
|
final List<Map<String, dynamic>> logs = [];
|
|
final entities = baseDir.listSync();
|
|
|
|
for (var entity in entities) {
|
|
if (entity is Directory) {
|
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
final content = await jsonFile.readAsString();
|
|
final data = jsonDecode(content) as Map<String, dynamic>;
|
|
data['logDirectory'] = entity.path;
|
|
logs.add(data);
|
|
}
|
|
}
|
|
}
|
|
return logs;
|
|
} catch (e) {
|
|
debugPrint("Error getting all in-situ logs: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Updates an existing marine in-situ log file with new submission status.
|
|
Future<void> updateInSituLog(Map<String, dynamic> updatedLogData) async {
|
|
final logDir = updatedLogData['logDirectory'];
|
|
if (logDir == null) {
|
|
debugPrint("Cannot update log: logDirectory key is missing.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
updatedLogData.remove('isResubmitting');
|
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
|
debugPrint("Log updated successfully at: ${jsonFile.path}");
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error updating in-situ log: $e");
|
|
}
|
|
}
|
|
|
|
// =======================================================================
|
|
// ADDED: Part 4: River In-Situ Specific Methods
|
|
// =======================================================================
|
|
|
|
/// Gets the base directory for storing river in-situ sampling data logs.
|
|
Future<Directory?> _getRiverInSituBaseDir() async {
|
|
final mmsv4Dir = await _getPublicMMSV4Directory();
|
|
if (mmsv4Dir == null) return null;
|
|
|
|
final inSituDir = Directory(p.join(mmsv4Dir.path, 'river', 'river_in_situ_sampling'));
|
|
if (!await inSituDir.exists()) {
|
|
await inSituDir.create(recursive: true);
|
|
}
|
|
return inSituDir;
|
|
}
|
|
|
|
/// Saves a single river in-situ sampling record to a unique folder in public storage.
|
|
Future<String?> saveRiverInSituSamplingData(RiverInSituSamplingData data) async {
|
|
final baseDir = await _getRiverInSituBaseDir();
|
|
if (baseDir == null) {
|
|
debugPrint("Could not get public storage directory for River In-Situ. Check permissions.");
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
final stationCode = data.selectedStation?['r_man_station_code'] ?? 'UNKNOWN_STATION';
|
|
final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
|
|
final eventFolderName = "${stationCode}_$timestamp";
|
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
|
|
|
if (!await eventDir.exists()) {
|
|
await eventDir.create(recursive: true);
|
|
}
|
|
|
|
final Map<String, dynamic> jsonData = { ...data.toApiFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };
|
|
|
|
jsonData['selectedStation'] = data.selectedStation;
|
|
|
|
final imageFiles = data.toApiImageFiles();
|
|
for (var entry in imageFiles.entries) {
|
|
final File? imageFile = entry.value;
|
|
if (imageFile != null) {
|
|
final String originalFileName = p.basename(imageFile.path);
|
|
final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
|
|
jsonData[entry.key] = newFile.path;
|
|
}
|
|
}
|
|
|
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
|
debugPrint("River In-Situ log saved to: ${jsonFile.path}");
|
|
|
|
return eventDir.path;
|
|
|
|
} catch (e) {
|
|
debugPrint("Error saving River In-Situ log to local storage: $e");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Retrieves all saved river in-situ submission logs from public storage.
|
|
Future<List<Map<String, dynamic>>> getAllRiverInSituLogs() async {
|
|
final baseDir = await _getRiverInSituBaseDir();
|
|
if (baseDir == null || !await baseDir.exists()) return [];
|
|
|
|
try {
|
|
final List<Map<String, dynamic>> logs = [];
|
|
final entities = baseDir.listSync();
|
|
|
|
for (var entity in entities) {
|
|
if (entity is Directory) {
|
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
final content = await jsonFile.readAsString();
|
|
final data = jsonDecode(content) as Map<String, dynamic>;
|
|
data['logDirectory'] = entity.path;
|
|
logs.add(data);
|
|
}
|
|
}
|
|
}
|
|
return logs;
|
|
} catch (e) {
|
|
debugPrint("Error getting all river in-situ logs: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Updates an existing river in-situ log file with new submission status.
|
|
Future<void> updateRiverInSituLog(Map<String, dynamic> updatedLogData) async {
|
|
final logDir = updatedLogData['logDirectory'];
|
|
if (logDir == null) {
|
|
debugPrint("Cannot update log: logDirectory key is missing.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
|
if (await jsonFile.exists()) {
|
|
updatedLogData.remove('isResubmitting');
|
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
|
debugPrint("Log updated successfully at: ${jsonFile.path}");
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error updating river in-situ log: $e");
|
|
}
|
|
}
|
|
} |