208 lines
7.3 KiB
Dart
208 lines
7.3 KiB
Dart
// lib/services/air_sampling_service.dart
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:image/image.dart' as img;
|
|
import 'package:intl/intl.dart';
|
|
|
|
// Import your actual data models
|
|
import '../models/air_installation_data.dart';
|
|
import '../models/air_collection_data.dart';
|
|
|
|
// Import a local storage service (you would create this)
|
|
// import 'local_storage_service.dart';
|
|
|
|
// A placeholder for your actual API service
|
|
class AirApiService {
|
|
Future<Map<String, dynamic>> submitInstallation({
|
|
required Map<String, dynamic> installationJson,
|
|
required List<File> imageFiles,
|
|
}) async {
|
|
// In a real app, you would build an http.MultipartRequest here
|
|
// to send the JSON data and image files to your server.
|
|
print("Submitting Installation to API...");
|
|
print("Data: $installationJson");
|
|
print("Image count: ${imageFiles.length}");
|
|
|
|
// Simulate a network call
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
// Simulate a successful response
|
|
return {
|
|
'status': 'S1', // 'S1' for Server Success (Installation Pending Collection)
|
|
'message': 'Installation data successfully submitted to the server.',
|
|
'refID': installationJson['refID'],
|
|
};
|
|
}
|
|
|
|
Future<Map<String, dynamic>> submitCollection({
|
|
required Map<String, dynamic> collectionJson,
|
|
required List<File> imageFiles,
|
|
}) async {
|
|
// In a real app, this would update the existing record linked by 'installationRefID'
|
|
print("Submitting Collection to API...");
|
|
print("Data: $collectionJson");
|
|
print("Image count: ${imageFiles.length}");
|
|
|
|
// Simulate a network call
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
// Simulate a successful response
|
|
return {
|
|
'status': 'S3', // 'S3' for Server Success (Completed)
|
|
'message': 'Collection data successfully linked and submitted.',
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/// A dedicated service to handle all business logic for the Air Manual Sampling feature.
|
|
class AirSamplingService {
|
|
final AirApiService _apiService = AirApiService();
|
|
// final LocalStorageService _localStorageService = LocalStorageService();
|
|
|
|
/// Picks an image from the specified source, adds a timestamp watermark,
|
|
/// and saves it to a temporary directory with a standardized name.
|
|
Future<File?> pickAndProcessImage(
|
|
ImageSource source, {
|
|
required String stationCode,
|
|
required String imageInfo, // e.g., "INSTALLATION_LEFT", "COLLECTION_CHART"
|
|
}) async {
|
|
final picker = ImagePicker();
|
|
final XFile? photo = await picker.pickImage(
|
|
source: source, imageQuality: 85, maxWidth: 1024);
|
|
if (photo == null) return null;
|
|
|
|
final bytes = await photo.readAsBytes();
|
|
img.Image? originalImage = img.decodeImage(bytes);
|
|
if (originalImage == null) return null;
|
|
|
|
// Prepare watermark text
|
|
final String watermarkTimestamp =
|
|
DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
|
|
final font = img.arial24;
|
|
|
|
// Add a white background for better text visibility
|
|
final textWidth = watermarkTimestamp.length * 12; // Estimate width
|
|
img.fillRect(originalImage,
|
|
x1: 5,
|
|
y1: 5,
|
|
x2: textWidth + 15,
|
|
y2: 35,
|
|
color: img.ColorRgb8(255, 255, 255));
|
|
img.drawString(originalImage, watermarkTimestamp,
|
|
font: font, x: 10, y: 10, color: img.ColorRgb8(0, 0, 0));
|
|
|
|
// Create a standardized file name
|
|
final tempDir = await getTemporaryDirectory();
|
|
final fileTimestamp = watermarkTimestamp.replaceAll(':', '-').replaceAll(' ', '_');
|
|
final newFileName =
|
|
"${stationCode}_${fileTimestamp}_${imageInfo.replaceAll(' ', '')}.jpg";
|
|
final filePath = path.join(tempDir.path, newFileName);
|
|
|
|
// Save the processed image and return the file
|
|
return File(filePath)..writeAsBytesSync(img.encodeJpg(originalImage));
|
|
}
|
|
|
|
/// Submits only the installation data.
|
|
Future<Map<String, dynamic>> submitInstallation(AirInstallationData data) async {
|
|
try {
|
|
// Prepare image files for upload
|
|
final List<File> images = [];
|
|
if (data.image1 != null) images.add(data.image1!);
|
|
if (data.image2 != null) images.add(data.image2!);
|
|
if (data.image3 != null) images.add(data.image3!);
|
|
if (data.image4 != null) images.add(data.image4!);
|
|
|
|
// In a real app, you would check connectivity here before attempting the API call
|
|
final result = await _apiService.submitInstallation(
|
|
installationJson: data.toJson(),
|
|
imageFiles: images,
|
|
);
|
|
return result;
|
|
} catch (e) {
|
|
print("API submission failed: $e. Saving installation locally.");
|
|
// --- Fallback to Local Storage ---
|
|
// TODO: Implement local DB save for installation data
|
|
data.status = 'L1'; // Mark as saved locally, pending collection
|
|
// await _localStorageService.saveAirInstallationData(data);
|
|
return {
|
|
'status': 'L1',
|
|
'message': 'Installation data saved locally.',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Submits only the collection data, linked to a previous installation.
|
|
Future<Map<String, dynamic>> submitCollection(AirCollectionData data) async {
|
|
try {
|
|
// Prepare image files for upload
|
|
final List<File> images = [];
|
|
if (data.imageSiteLeft != null) images.add(data.imageSiteLeft!);
|
|
if (data.imageSiteRight != null) images.add(data.imageSiteRight!);
|
|
// ... add all other collection images
|
|
|
|
// In a real app, you would check connectivity here
|
|
final result = await _apiService.submitCollection(
|
|
collectionJson: data.toJson(),
|
|
imageFiles: images,
|
|
);
|
|
return result;
|
|
} catch (e) {
|
|
print("API submission failed: $e. Saving collection locally.");
|
|
// --- Fallback to Local Storage ---
|
|
// TODO: Implement local DB save for collection data
|
|
data.status = 'L3'; // Mark as completed locally
|
|
// await _localStorageService.saveAirCollectionData(data);
|
|
return {
|
|
'status': 'L3',
|
|
'message': 'Collection data saved locally.',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Fetches installations that are pending collection.
|
|
Future<List<AirInstallationData>> getPendingInstallations() async {
|
|
// In a real app, this would query your local database or a server endpoint
|
|
// for records with status 'L1' (local, pending) or 'S1' (server, pending).
|
|
print("Fetching pending installations...");
|
|
await Future.delayed(const Duration(milliseconds: 500)); // Simulate network/DB delay
|
|
|
|
// Return placeholder data for demonstration
|
|
return [
|
|
AirInstallationData(
|
|
refID: 'ABC1234567',
|
|
stationID: 'SGR01',
|
|
locationName: 'Shah Alam',
|
|
samplingDate: '2025-08-10',
|
|
temp: '28.5',
|
|
status: 'L1', // Example of a locally saved installation
|
|
),
|
|
AirInstallationData(
|
|
refID: 'DEF8901234',
|
|
stationID: 'JHR02',
|
|
locationName: 'Muar',
|
|
samplingDate: '2025-08-09',
|
|
temp: '29.1',
|
|
status: 'S1', // Example of a server-saved installation
|
|
),
|
|
AirInstallationData(
|
|
refID: 'GHI5678901',
|
|
stationID: 'PRK01',
|
|
locationName: 'Ipoh',
|
|
samplingDate: '2025-08-11',
|
|
temp: '27.9',
|
|
status: 'S1',
|
|
),
|
|
];
|
|
}
|
|
|
|
void dispose() {
|
|
// Clean up any resources if necessary
|
|
}
|
|
}
|