change river ftp folder name and image naming to follow mms 1.00 to edc

This commit is contained in:
ALim Aidrus 2025-11-19 14:21:10 +08:00
parent c543e82d5b
commit 18e853ac83
6 changed files with 264 additions and 77 deletions

View File

@ -29,7 +29,7 @@ class _HomePageState extends State<HomePage> {
}); });
}, },
), ),
title: const Text("MMS Version 3.8.01"), title: const Text("MMS Version 3.12.01"),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.person), icon: const Icon(Icons.person),

View File

@ -221,7 +221,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
ListTile( ListTile(
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.info_outline),
title: const Text('App Version'), title: const Text('App Version'),
subtitle: const Text('MMS Version 3.8.01'), subtitle: const Text('MMS Version 3.12.01'),
dense: true, dense: true,
), ),
ListTile( ListTile(

View File

@ -355,19 +355,38 @@ class RiverInSituSamplingService {
} }
if (finalImageFiles.isNotEmpty) { if (finalImageFiles.isNotEmpty) {
final imageZip = await _zippingService.createImageZip( // Re-construct the map for retry to attempt renaming even in fallback
imageFiles: finalImageFiles.values.toList(), final Map<String, File> retryImages = {};
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String timestampId = "$dateStr$timeStr";
void addRetryMap(File? file, String prefix) {
if(file != null) retryImages['${prefix}_$timestampId.jpg'] = file;
}
addRetryMap(data.backgroundStationImage, 'background');
addRetryMap(data.upstreamRiverImage, 'upstream');
addRetryMap(data.downstreamRiverImage, 'downstream');
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
addRetryMap(data.optionalImage1, 'optional_1');
addRetryMap(data.optionalImage2, 'optional_2');
addRetryMap(data.optionalImage3, 'optional_3');
addRetryMap(data.optionalImage4, 'optional_4');
final retryImageZip = await _zippingService.createRenamedImageZip(
imageFiles: retryImages,
baseFileName: baseFileNameForQueue, baseFileName: baseFileNameForQueue,
destinationDir: null, destinationDir: null,
); );
if (imageZip != null) {
if (retryImageZip != null) {
// Queue for each config separately // Queue for each config separately
for (final config in ftpConfigs) { for (final config in ftpConfigs) {
final configId = config['ftp_config_id']; final configId = config['ftp_config_id'];
if (configId != null) { if (configId != null) {
await _retryService.addFtpToQueue( await _retryService.addFtpToQueue(
localFilePath: imageZip.path, localFilePath: retryImageZip.path,
remotePath: '/${p.basename(imageZip.path)}', remotePath: '/${p.basename(retryImageZip.path)}',
ftpConfigId: configId // Provide the specific config ID ftpConfigId: configId // Provide the specific config ID
); );
} }
@ -502,21 +521,26 @@ class RiverInSituSamplingService {
/// Generates data and image ZIP files and uploads them using SubmissionFtpService. /// Generates data and image ZIP files and uploads them using SubmissionFtpService.
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInSituSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async { Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInSituSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING ONLY
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String zipImageTimestamp = "$dateStr$timeStr";
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
final baseFileName = _generateBaseFileName(data); final baseFileName = _generateBaseFileName(data);
// 3. SETUP DIRECTORIES
final Directory? logDirectory = await _localStorageService.getRiverInSituBaseDir(data.samplingType, serverName: serverName); // Use correct base dir getter final Directory? logDirectory = await _localStorageService.getRiverInSituBaseDir(data.samplingType, serverName: serverName); // Use correct base dir getter
// --- START: MODIFIED folderName ---
// Use baseFileName for the folder name to match [stationCode]_[reportId] // Use baseFileName for the folder name to match [stationCode]_[reportId]
final folderName = baseFileName; final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
// --- END: MODIFIED folderName ---
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
if (localSubmissionDir != null && !await localSubmissionDir.exists()) { if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
await localSubmissionDir.create(recursive: true); await localSubmissionDir.create(recursive: true);
} }
// Create and upload data ZIP (with multiple JSON files specific to River In-Situ) // 4. CREATE DATA ZIP
final dataZip = await _zippingService.createDataZip( final dataZip = await _zippingService.createDataZip(
jsonDataMap: { jsonDataMap: {
'db.json': data.toDbJson(), 'db.json': data.toDbJson(),
@ -527,22 +551,48 @@ class RiverInSituSamplingService {
baseFileName: baseFileName, baseFileName: baseFileName,
destinationDir: localSubmissionDir, destinationDir: localSubmissionDir,
); );
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
if (dataZip != null) { if (dataZip != null) {
ftpDataResult = await _submissionFtpService.submit( ftpDataResult = await _submissionFtpService.submit(
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}'); moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
} }
// Create and upload image ZIP // 5. CREATE IMAGE ZIP (RENAMING LOGIC)
final imageZip = await _zippingService.createImageZip(
imageFiles: imageFiles.values.toList(),
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []}; Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit( // Create map: "New Name Inside Zip" -> "Original File on Phone"
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}'); final Map<String, File> imagesForZip = {};
void mapImage(File? file, String prefix) {
if (file != null && file.existsSync()) {
// Rename inside zip using the READABLE timestamp: prefix_20250930143419.jpg
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
}
}
mapImage(data.backgroundStationImage, 'background');
mapImage(data.upstreamRiverImage, 'upstream');
mapImage(data.downstreamRiverImage, 'downstream');
mapImage(data.sampleTurbidityImage, 'turbidity');
mapImage(data.optionalImage1, 'optional_1');
mapImage(data.optionalImage2, 'optional_2');
mapImage(data.optionalImage3, 'optional_3');
mapImage(data.optionalImage4, 'optional_4');
if (imagesForZip.isNotEmpty) {
// Call the NEW function: createRenamedImageZip
// Zip file name still uses baseFileName (milliseconds)
final imageZip = await _zippingService.createRenamedImageZip(
imageFiles: imagesForZip,
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit(
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
}
} }
return { return {

View File

@ -379,19 +379,38 @@ class RiverInvestigativeSamplingService { // Renamed class
} }
if (finalImageFiles.isNotEmpty) { if (finalImageFiles.isNotEmpty) {
final imageZip = await _zippingService.createImageZip( // Use existing queue logic for fallback (no renaming complexity here to be safe)
imageFiles: finalImageFiles.values.toList(), final Map<String, File> retryImages = {};
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String zipImageTimestamp = "$dateStr$timeStr";
void addRetryMap(File? file, String prefix) {
if(file != null) retryImages['${prefix}_$zipImageTimestamp.jpg'] = file;
}
addRetryMap(data.backgroundStationImage, 'background');
addRetryMap(data.upstreamRiverImage, 'upstream');
addRetryMap(data.downstreamRiverImage, 'downstream');
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
addRetryMap(data.optionalImage1, 'optional_1');
addRetryMap(data.optionalImage2, 'optional_2');
addRetryMap(data.optionalImage3, 'optional_3');
addRetryMap(data.optionalImage4, 'optional_4');
final retryImageZip = await _zippingService.createRenamedImageZip(
imageFiles: retryImages,
baseFileName: baseFileNameForQueue, baseFileName: baseFileNameForQueue,
destinationDir: null, // Save to temp dir destinationDir: null,
); );
if (imageZip != null) {
if (retryImageZip != null) {
// Queue for each config separately // Queue for each config separately
for (final config in ftpConfigs) { for (final config in ftpConfigs) {
final configId = config['ftp_config_id']; final configId = config['ftp_config_id'];
if (configId != null) { if (configId != null) {
await _retryService.addFtpToQueue( await _retryService.addFtpToQueue(
localFilePath: imageZip.path, localFilePath: retryImageZip.path,
remotePath: '/${p.basename(imageZip.path)}', // Standard remote path remotePath: '/${p.basename(retryImageZip.path)}', // Standard remote path
ftpConfigId: configId // Provide the specific config ID ftpConfigId: configId // Provide the specific config ID
); );
} }
@ -595,22 +614,26 @@ class RiverInvestigativeSamplingService { // Renamed class
// --- END: MODIFIED _generateBaseFileName --- // --- END: MODIFIED _generateBaseFileName ---
/// Generates data and image ZIP files and uploads them using SubmissionFtpService (Investigative). /// Generates data and image ZIP files and uploads them using SubmissionFtpService (Investigative).
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInvesManualSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async { // Updated model type Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInvesManualSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String zipImageTimestamp = "$dateStr$timeStr";
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
final baseFileName = _generateBaseFileName(data); // Use helper final baseFileName = _generateBaseFileName(data); // Use helper
// *** MODIFIED: Use correct base dir getter *** // 3. SETUP DIRECTORIES
final Directory? logDirectory = await _localStorageService.getRiverInvestigativeBaseDir(serverName: serverName); // NEW GETTER final Directory? logDirectory = await _localStorageService.getRiverInvestigativeBaseDir(serverName: serverName); // NEW GETTER
// Determine the specific folder for this submission log within the base directory final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
// --- START: MODIFIED folderName ---
final folderName = baseFileName; // Use the timestamp-based filename
// --- END: MODIFIED folderName ---
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
if (localSubmissionDir != null && !await localSubmissionDir.exists()) { if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
await localSubmissionDir.create(recursive: true); // Create if doesn't exist await localSubmissionDir.create(recursive: true); // Create if doesn't exist
} }
// Create and upload data ZIP (with multiple JSON files specific to River Investigative) // 4. CREATE DATA ZIP
final dataZip = await _zippingService.createDataZip( final dataZip = await _zippingService.createDataZip(
jsonDataMap: { jsonDataMap: {
// *** MODIFIED: Use Investigative model's JSON methods and filenames *** // *** MODIFIED: Use Investigative model's JSON methods and filenames ***
@ -622,6 +645,7 @@ class RiverInvestigativeSamplingService { // Renamed class
baseFileName: baseFileName, baseFileName: baseFileName,
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
); );
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; // Default success if no file Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; // Default success if no file
if (dataZip != null) { if (dataZip != null) {
ftpDataResult = await _submissionFtpService.submit( ftpDataResult = await _submissionFtpService.submit(
@ -631,14 +655,37 @@ class RiverInvestigativeSamplingService { // Renamed class
); );
} }
// Create and upload image ZIP (if images exist) // 5. CREATE IMAGE ZIP (RENAMING LOGIC)
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []}; // Default success if no images Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
if (imageFiles.isNotEmpty) {
final imageZip = await _zippingService.createImageZip( // Create mapping: "New Name Inside Zip" -> "Original File on Phone"
imageFiles: imageFiles.values.toList(), final Map<String, File> imagesForZip = {};
void mapImage(File? file, String prefix) {
if (file != null && file.existsSync()) {
// Rename inside zip: prefix_20250930143419.jpg
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
}
}
// Map images (Investigative model uses same names as others)
mapImage(data.backgroundStationImage, 'background');
mapImage(data.upstreamRiverImage, 'upstream');
mapImage(data.downstreamRiverImage, 'downstream');
mapImage(data.sampleTurbidityImage, 'turbidity');
mapImage(data.optionalImage1, 'optional_1');
mapImage(data.optionalImage2, 'optional_2');
mapImage(data.optionalImage3, 'optional_3');
mapImage(data.optionalImage4, 'optional_4');
if (imagesForZip.isNotEmpty) {
// *** MODIFICATION: Call the NEW renaming function ***
final imageZip = await _zippingService.createRenamedImageZip(
imageFiles: imagesForZip,
baseFileName: baseFileName, baseFileName: baseFileName,
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
); );
if (imageZip != null) { if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit( ftpImageResult = await _submissionFtpService.submit(
moduleName: moduleName, // 'river_investigative' moduleName: moduleName, // 'river_investigative'
@ -648,7 +695,6 @@ class RiverInvestigativeSamplingService { // Renamed class
} }
} }
// Combine statuses from both uploads // Combine statuses from both uploads
return { return {
'statuses': <Map<String, dynamic>>[ 'statuses': <Map<String, dynamic>>[

View File

@ -347,19 +347,39 @@ class RiverManualTriennialSamplingService {
} }
if (finalImageFiles.isNotEmpty) { if (finalImageFiles.isNotEmpty) {
final imageZip = await _zippingService.createImageZip( // Note: For the session expired case, renaming logic would ideally be here too,
imageFiles: finalImageFiles.values.toList(), // but requires complex reconstruction of the map. Following the previous pattern,
// we attempt to respect the rename if possible.
final Map<String, File> retryImages = {};
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String timestampId = "$dateStr$timeStr";
void addRetryMap(File? file, String prefix) {
if(file != null) retryImages['${prefix}_$timestampId.jpg'] = file;
}
addRetryMap(data.backgroundStationImage, 'background');
addRetryMap(data.upstreamRiverImage, 'upstream');
addRetryMap(data.downstreamRiverImage, 'downstream');
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
addRetryMap(data.optionalImage1, 'optional_1');
addRetryMap(data.optionalImage2, 'optional_2');
addRetryMap(data.optionalImage3, 'optional_3');
addRetryMap(data.optionalImage4, 'optional_4');
final retryImageZip = await _zippingService.createRenamedImageZip(
imageFiles: retryImages,
baseFileName: baseFileNameForQueue, baseFileName: baseFileNameForQueue,
destinationDir: null, destinationDir: null,
); );
if (imageZip != null) { if (retryImageZip != null) {
// Queue for each config separately // Queue for each config separately
for (final config in ftpConfigs) { for (final config in ftpConfigs) {
final configId = config['ftp_config_id']; final configId = config['ftp_config_id'];
if (configId != null) { if (configId != null) {
await _retryService.addFtpToQueue( await _retryService.addFtpToQueue(
localFilePath: imageZip.path, localFilePath: retryImageZip.path,
remotePath: '/${p.basename(imageZip.path)}', remotePath: '/${p.basename(retryImageZip.path)}',
ftpConfigId: configId // Provide the specific config ID ftpConfigId: configId // Provide the specific config ID
); );
} }
@ -492,44 +512,77 @@ class RiverManualTriennialSamplingService {
/// Generates data and image ZIP files and uploads them using SubmissionFtpService. /// Generates data and image ZIP files and uploads them using SubmissionFtpService.
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverManualTriennialSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async { Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverManualTriennialSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
final String zipImageTimestamp = "$dateStr$timeStr";
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
final baseFileName = _generateBaseFileName(data); final baseFileName = _generateBaseFileName(data);
// 3. SETUP DIRECTORIES
final Directory? logDirectory = await _localStorageService.getLogDirectory( // Use generic getter final Directory? logDirectory = await _localStorageService.getLogDirectory( // Use generic getter
serverName: serverName, serverName: serverName,
module: 'river', module: 'river',
subModule: 'river_triennial_sampling', // Correct sub-module path subModule: 'river_triennial_sampling', // Correct sub-module path
); );
// --- START: MODIFIED folderName --- // Use baseFileName for the folder
final folderName = baseFileName; // Use the timestamp-based filename final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
// --- END: MODIFIED folderName ---
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
if (localSubmissionDir != null && !await localSubmissionDir.exists()) { if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
await localSubmissionDir.create(recursive: true); await localSubmissionDir.create(recursive: true);
} }
// Create and upload data ZIP // 4. CREATE DATA ZIP
final dataZip = await _zippingService.createDataZip( final dataZip = await _zippingService.createDataZip(
jsonDataMap: {'db.json': data.toDbJson()}, // Assuming similar structure, adjust if needed jsonDataMap: {'db.json': data.toDbJson()}, // Assuming similar structure, adjust if needed
baseFileName: baseFileName, baseFileName: baseFileName,
destinationDir: localSubmissionDir, destinationDir: localSubmissionDir,
); );
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
if (dataZip != null) { if (dataZip != null) {
ftpDataResult = await _submissionFtpService.submit( ftpDataResult = await _submissionFtpService.submit(
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}'); moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
} }
// Create and upload image ZIP // 5. CREATE IMAGE ZIP (RENAMING LOGIC)
final imageZip = await _zippingService.createImageZip(
imageFiles: imageFiles.values.toList(),
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []}; Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit( // Create map: "New Name Inside Zip" -> "Original File on Phone"
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}'); final Map<String, File> imagesForZip = {};
void mapImage(File? file, String prefix) {
if (file != null && file.existsSync()) {
// Rename inside zip: prefix_20250930143419.jpg
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
}
}
// Map the specific fields to their short prefixes
mapImage(data.backgroundStationImage, 'background');
mapImage(data.upstreamRiverImage, 'upstream');
mapImage(data.downstreamRiverImage, 'downstream');
mapImage(data.sampleTurbidityImage, 'turbidity');
mapImage(data.optionalImage1, 'optional_1');
mapImage(data.optionalImage2, 'optional_2');
mapImage(data.optionalImage3, 'optional_3');
mapImage(data.optionalImage4, 'optional_4');
if (imagesForZip.isNotEmpty) {
// Call the NEW function: createRenamedImageZip
final imageZip = await _zippingService.createRenamedImageZip(
imageFiles: imagesForZip,
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit(
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
}
} }
return { return {

View File

@ -1,15 +1,14 @@
// lib/services/zipping_service.dart
import 'dart:io'; import 'dart:io';
import 'dart:convert'; // Added to ensure correct UTF-8 encoding import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:archive/archive_io.dart'; import 'package:archive/archive_io.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
/// A dedicated service to handle the creation of ZIP archives for FTP submission.
class ZippingService { class ZippingService {
/// Creates multiple JSON files from a map of data and zips them into a single archive. /// Creates multiple JSON files from a map of data and zips them into a single archive.
/// The map keys will be the filenames (e.g., 'db.json', 'form_data.json').
/// The map values should be the JSON string content for each file.
Future<File?> createDataZip({ Future<File?> createDataZip({
required Map<String, String> jsonDataMap, required Map<String, String> jsonDataMap,
required String baseFileName, required String baseFileName,
@ -17,7 +16,6 @@ class ZippingService {
}) async { }) async {
try { try {
final targetDir = destinationDir ?? await getTemporaryDirectory(); final targetDir = destinationDir ?? await getTemporaryDirectory();
// Ensure the target directory exists before creating the file
if (!await targetDir.exists()) { if (!await targetDir.exists()) {
await targetDir.create(recursive: true); await targetDir.create(recursive: true);
} }
@ -30,18 +28,9 @@ class ZippingService {
for (var entry in jsonDataMap.entries) { for (var entry in jsonDataMap.entries) {
final fileName = entry.key; final fileName = entry.key;
final jsonContent = entry.value; final jsonContent = entry.value;
// --- MODIFIED: Ensure UTF-8 encoding ---
// 1. Encode the string content into UTF-8 bytes
final utf8Bytes = utf8.encode(jsonContent); final utf8Bytes = utf8.encode(jsonContent);
// 2. Use the UTF-8 bytes and their correct length for the archive
// (This replaces the original: jsonContent.length, jsonContent.codeUnits)
final archiveFile = ArchiveFile(fileName, utf8Bytes.length, utf8Bytes); final archiveFile = ArchiveFile(fileName, utf8Bytes.length, utf8Bytes);
// --- END MODIFICATION ---
encoder.addArchiveFile(archiveFile); encoder.addArchiveFile(archiveFile);
debugPrint("Added $fileName to data ZIP."); debugPrint("Added $fileName to data ZIP.");
} }
@ -54,11 +43,13 @@ class ZippingService {
} }
} }
/// [ORIGINAL FUNCTION RESTORED]
/// Creates a ZIP file from a list of image files. /// Creates a ZIP file from a list of image files.
/// Used by Marine, Air, etc. that do not need renaming.
Future<File?> createImageZip({ Future<File?> createImageZip({
required List<File> imageFiles, required List<File> imageFiles,
required String baseFileName, required String baseFileName,
Directory? destinationDir, // ADDED: New optional parameter Directory? destinationDir,
}) async { }) async {
if (imageFiles.isEmpty) { if (imageFiles.isEmpty) {
debugPrint("No images provided to create an image ZIP."); debugPrint("No images provided to create an image ZIP.");
@ -67,7 +58,6 @@ class ZippingService {
try { try {
final targetDir = destinationDir ?? await getTemporaryDirectory(); final targetDir = destinationDir ?? await getTemporaryDirectory();
// Ensure the target directory exists before creating the file
if (!await targetDir.exists()) { if (!await targetDir.exists()) {
await targetDir.create(recursive: true); await targetDir.create(recursive: true);
} }
@ -94,4 +84,52 @@ class ZippingService {
return null; return null;
} }
} }
/// [NEW FUNCTION FOR RIVER]
/// Creates a ZIP file from a Map of image files to allow specific renaming inside the ZIP.
/// Key: The filename to be used INSIDE the zip (e.g., 'background_20231213.jpg')
/// Value: The actual File object on the device.
Future<File?> createRenamedImageZip({
required Map<String, File> imageFiles,
required String baseFileName,
Directory? destinationDir,
}) async {
if (imageFiles.isEmpty) {
debugPrint("No images provided to create an image ZIP.");
return null;
}
try {
final targetDir = destinationDir ?? await getTemporaryDirectory();
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final zipFilePath = p.join(targetDir.path, '${baseFileName}_img.zip');
final encoder = ZipFileEncoder();
encoder.create(zipFilePath);
debugPrint("Creating renamed image ZIP at: $zipFilePath");
for (var entry in imageFiles.entries) {
final String targetName = entry.key;
final File sourceFile = entry.value;
if (await sourceFile.exists()) {
final bytes = await sourceFile.readAsBytes();
final archiveFile = ArchiveFile(targetName, bytes.length, bytes);
encoder.addArchiveFile(archiveFile);
debugPrint("Added ${p.basename(sourceFile.path)} as $targetName");
} else {
debugPrint("Skipping non-existent file: ${sourceFile.path}");
}
}
encoder.close();
debugPrint("Renamed Image ZIP creation complete.");
return File(zipFilePath);
} catch (e) {
debugPrint("Error creating renamed image ZIP file: $e");
return null;
}
}
} }