// 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; InSituSamplingData({ this.samplingDate, this.samplingTime, }); /// Creates an InSituSamplingData object from a JSON map. /// This is critical for the offline retry mechanism. The keys used here MUST perfectly /// match the keys used in the `toDbJson()` method to ensure data integrity. 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(); // START FIX: Aligned all keys to perfectly match the toDbJson() method and added backward compatibility. 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']; 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 are added by LocalStorageService, not toDbJson, so they are read separately. data.leftLandViewImage = fileFromPath(json['man_left_side_land_view']); data.rightLandViewImage = fileFromPath(json['man_right_side_land_view']); 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']); // END FIX return data; } 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, }; } /// Creates a single JSON object with all submission data for offline storage. /// The keys here are the single source of truth for the offline data format. Map toDbJson() { return { 'first_sampler_name': firstSamplerName, 'first_sampler_user_id': firstSamplerUserId, 'secondSampler': secondSampler, 'sampling_date': samplingDate, 'sampling_time': samplingTime, '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, }; } }