// lib/models/in_situ_sampling_data.dart import 'dart:io'; import 'dart:convert'; // Added for jsonEncode /// A data model class to hold all information for the multi-step /// In-Situ Sampling form. class InSituSamplingData { // --- Step 1: Sampling & Station Info --- String? firstSamplerName; int? firstSamplerUserId; Map? secondSampler; String? samplingDate; String? samplingTime; String? samplingType; String? sampleIdCode; String? selectedStateName; String? selectedCategoryName; Map? selectedStation; String? stationLatitude; String? stationLongitude; String? currentLatitude; String? currentLongitude; double? distanceDifferenceInKm; String? distanceDifferenceRemarks; // --- Step 2: Site Info & Photos --- String? weather; String? tideLevel; String? seaCondition; String? eventRemarks; String? labRemarks; File? leftLandViewImage; File? rightLandViewImage; File? waterFillingImage; File? seawaterColorImage; File? phPaperImage; File? optionalImage1; String? optionalRemark1; File? optionalImage2; String? optionalRemark2; File? optionalImage3; String? optionalRemark3; File? optionalImage4; String? optionalRemark4; // --- Step 3: Data Capture --- String? sondeId; String? dataCaptureDate; String? dataCaptureTime; double? oxygenConcentration; double? oxygenSaturation; double? ph; double? salinity; double? electricalConductivity; double? temperature; double? tds; double? turbidity; double? tss; double? batteryVoltage; // --- Post-Submission Status --- String? submissionStatus; String? submissionMessage; String? reportId; // --- START: NPE Report Compatibility Fields --- /// Fields to hold data that can be transferred to an NPE Report. /// This makes the model compatible for auto-generating NPE reports in the future. // Corresponds to the checkboxes in the NPE form Map npeFieldObservations = { 'Oil slick on the water surface/ Oil spill': false, 'Discoloration of the sea water': false, 'Formation of foam on the surface': false, 'Coral bleaching or dead corals': false, 'Observation of tar balls': false, 'Excessive debris': false, 'Red tides or algae blooms': false, 'Silt plume': false, 'Foul smell': false, 'Others': false, }; // Corresponds to the "Others" text field in NPE observations String? npeOthersObservationRemark; // Corresponds to the "Possible Source" field in the NPE form String? npePossibleSource; // Holds the images to be attached to the NPE report File? npeImage1; File? npeImage2; File? npeImage3; File? npeImage4; // --- END: NPE Report Compatibility Fields --- InSituSamplingData({ this.samplingDate, this.samplingTime, }); /// Creates an InSituSamplingData object from a JSON map. factory InSituSamplingData.fromJson(Map json) { double? doubleFromJson(dynamic value) { if (value is num) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } int? intFromJson(dynamic value) { if (value is int) return value; if (value is String) return int.tryParse(value); return null; } File? fileFromPath(dynamic path) { return (path is String && path.isNotEmpty) ? File(path) : null; } final data = InSituSamplingData(); // Standard In-Situ Fields data.firstSamplerName = json['first_sampler_name']; data.firstSamplerUserId = intFromJson(json['first_sampler_user_id']); data.secondSampler = json['secondSampler'] ?? json['second_sampler']; data.samplingDate = json['sampling_date'] ?? json['man_date']; data.samplingTime = json['sampling_time'] ?? json['man_time']; data.samplingType = json['sampling_type']; // ... (all other existing fields) data.sampleIdCode = json['sample_id_code']; data.selectedStateName = json['selected_state_name']; data.selectedCategoryName = json['selected_category_name']; data.selectedStation = json['selectedStation']; data.stationLatitude = json['station_latitude']; data.stationLongitude = json['station_longitude']; data.currentLatitude = json['current_latitude']?.toString(); data.currentLongitude = json['current_longitude']?.toString(); data.distanceDifferenceInKm = doubleFromJson(json['distance_difference_in_km']); data.distanceDifferenceRemarks = json['distance_difference_remarks']; data.weather = json['weather']; data.tideLevel = json['tide_level']; data.seaCondition = json['sea_condition']; data.eventRemarks = json['event_remarks']; data.labRemarks = json['lab_remarks']; data.optionalRemark1 = json['man_optional_photo_01_remarks']; data.optionalRemark2 = json['man_optional_photo_02_remarks']; data.optionalRemark3 = json['man_optional_photo_03_remarks']; data.optionalRemark4 = json['man_optional_photo_04_remarks']; data.sondeId = json['sonde_id']; data.dataCaptureDate = json['data_capture_date']; data.dataCaptureTime = json['data_capture_time']; data.oxygenConcentration = doubleFromJson(json['oxygen_concentration']); data.oxygenSaturation = doubleFromJson(json['oxygen_saturation']); data.ph = doubleFromJson(json['ph']); data.salinity = doubleFromJson(json['salinity']); data.electricalConductivity = doubleFromJson(json['electrical_conductivity']); data.temperature = doubleFromJson(json['temperature']); data.tds = doubleFromJson(json['tds']); data.turbidity = doubleFromJson(json['turbidity']); data.tss = doubleFromJson(json['tss']); data.batteryVoltage = doubleFromJson(json['battery_voltage']); data.submissionStatus = json['submission_status']; data.submissionMessage = json['submission_message']; data.reportId = json['report_id']?.toString(); // Image paths (handled by LocalStorageService) data.leftLandViewImage = fileFromPath(json['man_left_side_land_view']); data.rightLandViewImage = fileFromPath(json['man_right_side_land_view']); // ... (all other existing images) data.waterFillingImage = fileFromPath(json['man_filling_water_into_sample_bottle']); data.seawaterColorImage = fileFromPath(json['man_seawater_in_clear_glass_bottle']); data.phPaperImage = fileFromPath(json['man_examine_preservative_ph_paper']); data.optionalImage1 = fileFromPath(json['man_optional_photo_01']); data.optionalImage2 = fileFromPath(json['man_optional_photo_02']); data.optionalImage3 = fileFromPath(json['man_optional_photo_03']); data.optionalImage4 = fileFromPath(json['man_optional_photo_04']); // --- START: Deserialization for NPE Fields --- if (json['npe_field_observations'] is Map) { data.npeFieldObservations = Map.from(json['npe_field_observations']); } data.npeOthersObservationRemark = json['npe_others_observation_remark']; data.npePossibleSource = json['npe_possible_source']; // NPE image paths data.npeImage1 = fileFromPath(json['npe_image_1']); data.npeImage2 = fileFromPath(json['npe_image_2']); data.npeImage3 = fileFromPath(json['npe_image_3']); data.npeImage4 = fileFromPath(json['npe_image_4']); // --- END: Deserialization for NPE Fields --- return data; } // ... (generateTelegramAlertMessage method remains unchanged) ... // ... (toApiFormData method remains unchanged) ... // ... (toApiImageFiles method remains unchanged) ... /// Creates a single JSON object with all submission data for offline storage. Map toDbJson() { return { 'first_sampler_name': firstSamplerName, 'first_sampler_user_id': firstSamplerUserId, 'secondSampler': secondSampler, 'sampling_date': samplingDate, 'sampling_time': samplingTime, // ... (all other existing fields) 'sampling_type': samplingType, 'sample_id_code': sampleIdCode, 'selected_state_name': selectedStateName, 'selected_category_name': selectedCategoryName, 'selectedStation': selectedStation, 'station_latitude': stationLatitude, 'station_longitude': stationLongitude, 'current_latitude': currentLatitude, 'current_longitude': currentLongitude, 'distance_difference_in_km': distanceDifferenceInKm, 'distance_difference_remarks': distanceDifferenceRemarks, 'weather': weather, 'tide_level': tideLevel, 'sea_condition': seaCondition, 'event_remarks': eventRemarks, 'lab_remarks': labRemarks, 'man_optional_photo_01_remarks': optionalRemark1, 'man_optional_photo_02_remarks': optionalRemark2, 'man_optional_photo_03_remarks': optionalRemark3, 'man_optional_photo_04_remarks': optionalRemark4, 'sonde_id': sondeId, 'data_capture_date': dataCaptureDate, 'data_capture_time': dataCaptureTime, 'oxygen_concentration': oxygenConcentration, 'oxygen_saturation': oxygenSaturation, 'ph': ph, 'salinity': salinity, 'electrical_conductivity': electricalConductivity, 'temperature': temperature, 'tds': tds, 'turbidity': turbidity, 'tss': tss, 'battery_voltage': batteryVoltage, 'submission_status': submissionStatus, 'submission_message': submissionMessage, 'report_id': reportId, // --- START: Serialization for NPE Fields --- 'npe_field_observations': npeFieldObservations, 'npe_others_observation_remark': npeOthersObservationRemark, 'npe_possible_source': npePossibleSource, // Note: Image file paths are handled separately by the LocalStorageService // and are not part of this JSON object directly. // --- END: Serialization for NPE Fields --- }; } // --- Methods from the original file --- String generateTelegramAlertMessage({required bool isDataOnly}) { final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)"; final stationName = selectedStation?['man_station_name'] ?? 'N/A'; final stationCode = selectedStation?['man_station_code'] ?? 'N/A'; final buffer = StringBuffer() ..writeln('✅ *In-Situ Sample $submissionType Submitted:*') ..writeln() ..writeln('*Station Name & Code:* $stationName ($stationCode)') ..writeln('*Date of Submission:* $samplingDate') ..writeln('*Submitted by User:* $firstSamplerName') ..writeln('*Sonde ID:* ${sondeId ?? "N/A"}') ..writeln('*Status of Submission:* Successful'); if (distanceDifferenceInKm != null && distanceDifferenceInKm! > 0) { buffer ..writeln() ..writeln('🔔 *Alert:*') ..writeln('*Distance from station:* ${(distanceDifferenceInKm! * 1000).toStringAsFixed(0)} meters'); if (distanceDifferenceRemarks != null && distanceDifferenceRemarks!.isNotEmpty) { buffer.writeln('*Remarks for distance:* $distanceDifferenceRemarks'); } } return buffer.toString(); } Map toApiFormData() { final Map map = {}; void add(String key, dynamic value) { if (value != null) { String stringValue; if (value is double) { if (value == -999.0) { stringValue = '-999'; } else { stringValue = value.toStringAsFixed(5); } } else { stringValue = value.toString(); } if (stringValue.isNotEmpty) { map[key] = stringValue; } } } add('station_id', selectedStation?['station_id']); add('man_date', samplingDate); add('man_time', samplingTime); add('first_sampler_user_id', firstSamplerUserId); add('man_second_sampler_id', secondSampler?['user_id']); add('man_type', samplingType); add('man_sample_id_code', sampleIdCode); add('man_current_latitude', currentLatitude); add('man_current_longitude', currentLongitude); add('man_distance_difference', distanceDifferenceInKm); add('man_distance_difference_remarks', distanceDifferenceRemarks); add('man_weather', weather); add('man_tide_level', tideLevel); add('man_sea_condition', seaCondition); add('man_event_remark', eventRemarks); add('man_lab_remark', labRemarks); add('man_optional_photo_01_remarks', optionalRemark1); add('man_optional_photo_02_remarks', optionalRemark2); add('man_optional_photo_03_remarks', optionalRemark3); add('man_optional_photo_04_remarks', optionalRemark4); add('man_sondeID', sondeId); add('data_capture_date', dataCaptureDate); add('data_capture_time', dataCaptureTime); add('man_oxygen_conc', oxygenConcentration); add('man_oxygen_sat', oxygenSaturation); add('man_ph', ph); add('man_salinity', salinity); add('man_conductivity', electricalConductivity); add('man_temperature', temperature); add('man_tds', tds); add('man_turbidity', turbidity); add('man_tss', tss); add('man_battery_volt', batteryVoltage); add('first_sampler_name', firstSamplerName); add('man_station_code', selectedStation?['man_station_code']); add('man_station_name', selectedStation?['man_station_name']); return map; } Map toApiImageFiles() { return { 'man_left_side_land_view': leftLandViewImage, 'man_right_side_land_view': rightLandViewImage, 'man_filling_water_into_sample_bottle': waterFillingImage, 'man_seawater_in_clear_glass_bottle': seawaterColorImage, 'man_examine_preservative_ph_paper': phPaperImage, 'man_optional_photo_01': optionalImage1, 'man_optional_photo_02': optionalImage2, 'man_optional_photo_03': optionalImage3, 'man_optional_photo_04': optionalImage4, }; } }