modfiy marine npe report to properly send api and telegram alert to server
This commit is contained in:
parent
cf22668576
commit
d0f9d72ebd
252
lib/main.dart
252
lib/main.dart
@ -12,7 +12,6 @@ import 'package:environment_monitoring_app/services/database_helper.dart';
|
|||||||
import 'package:environment_monitoring_app/services/local_storage_service.dart';
|
import 'package:environment_monitoring_app/services/local_storage_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/river_manual_triennial_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/river_manual_triennial_sampling_service.dart';
|
||||||
// *** ADDED: Import River Investigative Sampling Service ***
|
|
||||||
import 'package:environment_monitoring_app/services/river_investigative_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/river_investigative_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/air_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/air_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/telegram_service.dart';
|
import 'package:environment_monitoring_app/services/telegram_service.dart';
|
||||||
@ -21,7 +20,7 @@ import 'package:environment_monitoring_app/services/retry_service.dart';
|
|||||||
import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_investigative_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/marine_investigative_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_npe_report_service.dart';
|
import 'package:environment_monitoring_app/services/marine_npe_report_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_tarball_sampling_service.dart'; // Ensure this import is present
|
import 'package:environment_monitoring_app/services/marine_tarball_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_manual_pre_departure_service.dart';
|
import 'package:environment_monitoring_app/services/marine_manual_pre_departure_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_manual_sonde_calibration_service.dart';
|
import 'package:environment_monitoring_app/services/marine_manual_sonde_calibration_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_manual_equipment_maintenance_service.dart';
|
import 'package:environment_monitoring_app/services/marine_manual_equipment_maintenance_service.dart';
|
||||||
@ -38,14 +37,13 @@ import 'package:environment_monitoring_app/home_page.dart';
|
|||||||
import 'package:environment_monitoring_app/screens/profile.dart';
|
import 'package:environment_monitoring_app/screens/profile.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings.dart';
|
import 'package:environment_monitoring_app/screens/settings.dart';
|
||||||
|
|
||||||
// --- START: New Settings Screen Imports ---
|
// Settings Screen Imports
|
||||||
import 'package:environment_monitoring_app/screens/settings/submission_preferences_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/submission_preferences_settings.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings/telegram_alert_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/telegram_alert_settings.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings/api_ftp_configurations_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/api_ftp_configurations_settings.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings/parameter_limits_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/parameter_limits_settings.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings/air_clients_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/air_clients_settings.dart';
|
||||||
import 'package:environment_monitoring_app/screens/settings/station_info_settings.dart';
|
import 'package:environment_monitoring_app/screens/settings/station_info_settings.dart';
|
||||||
// --- END: New Settings Screen Imports ---
|
|
||||||
|
|
||||||
// Department Home Pages
|
// Department Home Pages
|
||||||
import 'package:environment_monitoring_app/screens/air/air_home_page.dart';
|
import 'package:environment_monitoring_app/screens/air/air_home_page.dart';
|
||||||
@ -80,14 +78,9 @@ import 'package:environment_monitoring_app/screens/river/continuous/overview.dar
|
|||||||
import 'package:environment_monitoring_app/screens/river/continuous/entry.dart' as riverContinuousEntry;
|
import 'package:environment_monitoring_app/screens/river/continuous/entry.dart' as riverContinuousEntry;
|
||||||
import 'package:environment_monitoring_app/screens/river/continuous/report.dart' as riverContinuousReport;
|
import 'package:environment_monitoring_app/screens/river/continuous/report.dart' as riverContinuousReport;
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_info_centre_document.dart';
|
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_info_centre_document.dart';
|
||||||
// *** ADDED: Import River Investigative Manual Sampling Screen ***
|
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_manual_sampling.dart' as riverInvestigativeManualSampling;
|
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_manual_sampling.dart' as riverInvestigativeManualSampling;
|
||||||
// *** START: ADDED NEW RIVER INVESTIGATIVE IMPORTS ***
|
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_data_status_log.dart' as riverInvestigativeDataStatusLog;
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_data_status_log.dart'
|
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_image_request.dart' as riverInvestigativeImageRequest;
|
||||||
as riverInvestigativeDataStatusLog;
|
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/river_investigative_image_request.dart'
|
|
||||||
as riverInvestigativeImageRequest;
|
|
||||||
// *** END: ADDED NEW RIVER INVESTIGATIVE IMPORTS ***
|
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/overview.dart' as riverInvestigativeOverview;
|
import 'package:environment_monitoring_app/screens/river/investigative/overview.dart' as riverInvestigativeOverview;
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/entry.dart' as riverInvestigativeEntry;
|
import 'package:environment_monitoring_app/screens/river/investigative/entry.dart' as riverInvestigativeEntry;
|
||||||
import 'package:environment_monitoring_app/screens/river/investigative/report.dart' as riverInvestigativeReport;
|
import 'package:environment_monitoring_app/screens/river/investigative/report.dart' as riverInvestigativeReport;
|
||||||
@ -98,32 +91,20 @@ import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_p
|
|||||||
import 'package:environment_monitoring_app/screens/marine/manual/in_situ_sampling.dart' as marineManualInSituSampling;
|
import 'package:environment_monitoring_app/screens/marine/manual/in_situ_sampling.dart' as marineManualInSituSampling;
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_report.dart' as marineManualReport;
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_report.dart' as marineManualReport;
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_npe_report_hub.dart';
|
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_npe_report_hub.dart';
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_pre_departure_checklist_screen.dart'
|
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_pre_departure_checklist_screen.dart' as marineManualPreDepartureChecklist;
|
||||||
as marineManualPreDepartureChecklist;
|
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_sonde_calibration_screen.dart' as marineManualSondeCalibration;
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_sonde_calibration_screen.dart'
|
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_equipment_maintenance_screen.dart' as marineManualEquipmentMaintenance;
|
||||||
as marineManualSondeCalibration;
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_data_status_log.dart' as marineManualDataStatusLog;
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_manual_equipment_maintenance_screen.dart'
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_report_status_log.dart' as marineManualReportStatusLog;
|
||||||
as marineManualEquipmentMaintenance;
|
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_data_status_log.dart'
|
|
||||||
as marineManualDataStatusLog;
|
|
||||||
// *** START: ADDED NEW IMPORT ***
|
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_report_status_log.dart'
|
|
||||||
as marineManualReportStatusLog;
|
|
||||||
// *** END: ADDED NEW IMPORT ***
|
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_image_request.dart' as marineManualImageRequest;
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_image_request.dart' as marineManualImageRequest;
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/marine_continuous_info_centre_document.dart';
|
import 'package:environment_monitoring_app/screens/marine/continuous/marine_continuous_info_centre_document.dart';
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/overview.dart' as marineContinuousOverview;
|
import 'package:environment_monitoring_app/screens/marine/continuous/overview.dart' as marineContinuousOverview;
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/entry.dart' as marineContinuousEntry;
|
import 'package:environment_monitoring_app/screens/marine/continuous/entry.dart' as marineContinuousEntry;
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/report.dart' as marineContinuousReport;
|
import 'package:environment_monitoring_app/screens/marine/continuous/report.dart' as marineContinuousReport;
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_info_centre_document.dart';
|
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_info_centre_document.dart';
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_manual_sampling.dart'
|
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_manual_sampling.dart' as marineInvestigativeManualSampling;
|
||||||
as marineInvestigativeManualSampling;
|
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_data_status_log.dart' as marineInvestigativeDataStatusLog;
|
||||||
// *** START: ADDED NEW MARINE INVESTIGATIVE IMPORTS ***
|
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_image_request.dart' as marineInvestigativeImageRequest;
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_data_status_log.dart'
|
|
||||||
as marineInvestigativeDataStatusLog;
|
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/marine_investigative_image_request.dart'
|
|
||||||
as marineInvestigativeImageRequest;
|
|
||||||
// *** END: ADDED NEW MARINE INVESTIGATIVE IMPORTS ***
|
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/overview.dart' as marineInvestigativeOverview;
|
import 'package:environment_monitoring_app/screens/marine/investigative/overview.dart' as marineInvestigativeOverview;
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/entry.dart' as marineInvestigativeEntry;
|
import 'package:environment_monitoring_app/screens/marine/investigative/entry.dart' as marineInvestigativeEntry;
|
||||||
import 'package:environment_monitoring_app/screens/marine/investigative/report.dart' as marineInvestigativeReport;
|
import 'package:environment_monitoring_app/screens/marine/investigative/report.dart' as marineInvestigativeReport;
|
||||||
@ -141,15 +122,21 @@ void main() async {
|
|||||||
final TelegramService telegramService = TelegramService();
|
final TelegramService telegramService = TelegramService();
|
||||||
final ApiService apiService = ApiService(telegramService: telegramService);
|
final ApiService apiService = ApiService(telegramService: telegramService);
|
||||||
final RetryService retryService = RetryService();
|
final RetryService retryService = RetryService();
|
||||||
|
|
||||||
|
// Sampling Services
|
||||||
final MarineInSituSamplingService marineInSituService = MarineInSituSamplingService(telegramService);
|
final MarineInSituSamplingService marineInSituService = MarineInSituSamplingService(telegramService);
|
||||||
final RiverInSituSamplingService riverInSituService = RiverInSituSamplingService(telegramService);
|
final RiverInSituSamplingService riverInSituService = RiverInSituSamplingService(telegramService);
|
||||||
final MarineInvestigativeSamplingService marineInvestigativeService =
|
final MarineInvestigativeSamplingService marineInvestigativeService = MarineInvestigativeSamplingService(telegramService);
|
||||||
MarineInvestigativeSamplingService(telegramService);
|
final RiverInvestigativeSamplingService riverInvestigativeService = RiverInvestigativeSamplingService(telegramService);
|
||||||
// *** ADDED: Create instance of RiverInvestigativeSamplingService ***
|
|
||||||
final RiverInvestigativeSamplingService riverInvestigativeService =
|
|
||||||
RiverInvestigativeSamplingService(telegramService);
|
|
||||||
final MarineTarballSamplingService marineTarballService = MarineTarballSamplingService(telegramService);
|
final MarineTarballSamplingService marineTarballService = MarineTarballSamplingService(telegramService);
|
||||||
|
|
||||||
|
// --- START: Instantiate Marine Report Services ---
|
||||||
|
final MarineNpeReportService marineNpeService = MarineNpeReportService(telegramService);
|
||||||
|
final MarineManualPreDepartureService marinePreDepartureService = MarineManualPreDepartureService(apiService);
|
||||||
|
final MarineManualSondeCalibrationService marineSondeCalibrationService = MarineManualSondeCalibrationService(apiService);
|
||||||
|
final MarineManualEquipmentMaintenanceService marineEquipmentMaintenanceService = MarineManualEquipmentMaintenanceService(apiService);
|
||||||
|
// --- END: Instantiate Marine Report Services ---
|
||||||
|
|
||||||
telegramService.setApiService(apiService);
|
telegramService.setApiService(apiService);
|
||||||
|
|
||||||
// The AuthProvider needs to be created here so it can be passed to the retry service.
|
// The AuthProvider needs to be created here so it can be passed to the retry service.
|
||||||
@ -161,13 +148,20 @@ void main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Initialize the retry service with all its dependencies.
|
// Initialize the retry service with all its dependencies.
|
||||||
// *** MODIFIED: Added riverInvestigativeService dependency (and marineTarballService from previous request) ***
|
|
||||||
retryService.initialize(
|
retryService.initialize(
|
||||||
marineInSituService: marineInSituService,
|
marineInSituService: marineInSituService,
|
||||||
riverInSituService: riverInSituService,
|
riverInSituService: riverInSituService,
|
||||||
marineInvestigativeService: marineInvestigativeService,
|
marineInvestigativeService: marineInvestigativeService,
|
||||||
riverInvestigativeService: riverInvestigativeService, // <-- Added this line
|
riverInvestigativeService: riverInvestigativeService,
|
||||||
marineTarballService: marineTarballService,
|
marineTarballService: marineTarballService,
|
||||||
|
|
||||||
|
// --- START: Pass the new services to initialize ---
|
||||||
|
marineNpeService: marineNpeService,
|
||||||
|
marinePreDepartureService: marinePreDepartureService,
|
||||||
|
marineSondeCalibrationService: marineSondeCalibrationService,
|
||||||
|
marineEquipmentMaintenanceService: marineEquipmentMaintenanceService,
|
||||||
|
// --- END: Pass the new services ---
|
||||||
|
|
||||||
authProvider: authProvider,
|
authProvider: authProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -182,24 +176,23 @@ void main() async {
|
|||||||
Provider<TelegramService>(create: (_) => telegramService),
|
Provider<TelegramService>(create: (_) => telegramService),
|
||||||
Provider(create: (_) => LocalStorageService()),
|
Provider(create: (_) => LocalStorageService()),
|
||||||
Provider.value(value: retryService),
|
Provider.value(value: retryService),
|
||||||
|
|
||||||
|
// Sampling Services
|
||||||
Provider.value(value: marineInSituService),
|
Provider.value(value: marineInSituService),
|
||||||
Provider.value(value: marineInvestigativeService),
|
Provider.value(value: marineInvestigativeService),
|
||||||
Provider.value(value: riverInSituService),
|
Provider.value(value: riverInSituService),
|
||||||
// *** ADDED: Provider for River Investigative Service ***
|
Provider.value(value: riverInvestigativeService),
|
||||||
Provider.value(value: riverInvestigativeService), // Use Provider.value
|
Provider.value(value: marineTarballService),
|
||||||
|
|
||||||
|
// Report Services (Use Provider.value since they are already instantiated)
|
||||||
|
Provider.value(value: marineNpeService),
|
||||||
|
Provider.value(value: marinePreDepartureService),
|
||||||
|
Provider.value(value: marineSondeCalibrationService),
|
||||||
|
Provider.value(value: marineEquipmentMaintenanceService),
|
||||||
|
|
||||||
|
// Other Independent Services
|
||||||
Provider(create: (context) => RiverManualTriennialSamplingService(telegramService)),
|
Provider(create: (context) => RiverManualTriennialSamplingService(telegramService)),
|
||||||
Provider(create: (context) => AirSamplingService(databaseHelper, telegramService)),
|
Provider(create: (context) => AirSamplingService(databaseHelper, telegramService)),
|
||||||
Provider.value(value: marineTarballService), // Use Provider.value
|
|
||||||
Provider(create: (context) => MarineNpeReportService(Provider.of<TelegramService>(context, listen: false))),
|
|
||||||
Provider(
|
|
||||||
create: (context) =>
|
|
||||||
MarineManualPreDepartureService(Provider.of<ApiService>(context, listen: false))),
|
|
||||||
Provider(
|
|
||||||
create: (context) =>
|
|
||||||
MarineManualSondeCalibrationService(Provider.of<ApiService>(context, listen: false))),
|
|
||||||
Provider(
|
|
||||||
create: (context) =>
|
|
||||||
MarineManualEquipmentMaintenanceService(Provider.of<ApiService>(context, listen: false))),
|
|
||||||
],
|
],
|
||||||
child: const RootApp(),
|
child: const RootApp(),
|
||||||
),
|
),
|
||||||
@ -232,67 +225,49 @@ class RootApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RootAppState extends State<RootApp> {
|
class _RootAppState extends State<RootApp> {
|
||||||
// --- START: MODIFICATION FOR HOURLY SYNC ---
|
|
||||||
Timer? _configSyncTimer;
|
Timer? _configSyncTimer;
|
||||||
// --- END: MODIFICATION ---
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initializeConnectivityListener();
|
_initializeConnectivityListener();
|
||||||
_performInitialSessionCheck();
|
_performInitialSessionCheck();
|
||||||
// --- START: MODIFICATION FOR HOURLY SYNC ---
|
_initializePeriodicSync();
|
||||||
_initializePeriodicSync(); // Start the hourly sync timer
|
|
||||||
// --- END: MODIFICATION ---
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: MODIFICATION FOR HOURLY SYNC ---
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_configSyncTimer?.cancel(); // Cancel the timer when the app closes
|
_configSyncTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
// --- END: MODIFICATION ---
|
|
||||||
|
|
||||||
|
|
||||||
/// Initial check when app loads to see if we need to transition from offline to online.
|
|
||||||
void _performInitialSessionCheck() async {
|
void _performInitialSessionCheck() async {
|
||||||
// Wait a moment for providers to be fully available.
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||||
|
|
||||||
// Perform proactive token refresh on app start
|
|
||||||
await authProvider.proactiveTokenRefresh();
|
await authProvider.proactiveTokenRefresh();
|
||||||
|
|
||||||
// First, try to transition from an offline placeholder token to an online one.
|
|
||||||
final didTransition = await authProvider.checkAndTransitionToOnlineSession();
|
final didTransition = await authProvider.checkAndTransitionToOnlineSession();
|
||||||
// If no transition happened (i.e., we were already supposed to be online), validate the session.
|
|
||||||
if (!didTransition) {
|
if (!didTransition) {
|
||||||
authProvider.validateAndRefreshSession();
|
authProvider.validateAndRefreshSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listens for connectivity changes to trigger auto-relogin or queue processing.
|
|
||||||
void _initializeConnectivityListener() {
|
void _initializeConnectivityListener() {
|
||||||
Connectivity().onConnectivityChanged.listen((List<ConnectivityResult> results) {
|
Connectivity().onConnectivityChanged.listen((List<ConnectivityResult> results) {
|
||||||
if (!results.contains(ConnectivityResult.none)) {
|
if (!results.contains(ConnectivityResult.none)) {
|
||||||
debugPrint("[Main] Internet connection detected.");
|
debugPrint("[Main] Internet connection detected.");
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Access services from provider context
|
|
||||||
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||||
final telegramService = Provider.of<TelegramService>(context, listen: false);
|
final telegramService = Provider.of<TelegramService>(context, listen: false);
|
||||||
final retryService = Provider.of<RetryService>(context, listen: false);
|
final retryService = Provider.of<RetryService>(context, listen: false);
|
||||||
|
|
||||||
// When connection is restored, always try to transition/validate the session.
|
|
||||||
authProvider.checkAndTransitionToOnlineSession().then((didTransition) {
|
authProvider.checkAndTransitionToOnlineSession().then((didTransition) {
|
||||||
if (!didTransition) {
|
if (!didTransition) {
|
||||||
authProvider.validateAndRefreshSession();
|
authProvider.validateAndRefreshSession();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process queues
|
|
||||||
telegramService.processAlertQueue();
|
telegramService.processAlertQueue();
|
||||||
retryService.processRetryQueue();
|
retryService.processRetryQueue();
|
||||||
}
|
}
|
||||||
@ -302,22 +277,13 @@ class _RootAppState extends State<RootApp> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: MODIFICATION FOR HOURLY SYNC ---
|
|
||||||
/// Initializes a recurring timer to sync data periodically.
|
|
||||||
void _initializePeriodicSync() {
|
void _initializePeriodicSync() {
|
||||||
// Start a timer for 1 hour (as requested). You can change this duration.
|
|
||||||
_configSyncTimer = Timer.periodic(const Duration(hours: 1), (timer) {
|
_configSyncTimer = Timer.periodic(const Duration(hours: 1), (timer) {
|
||||||
debugPrint("[Main] Periodic 1-hour sync triggered.");
|
debugPrint("[Main] Periodic 1-hour sync triggered.");
|
||||||
|
|
||||||
// Use 'context.read' for a safe, one-time read inside a timer
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
final authProvider = context.read<AuthProvider>();
|
final authProvider = context.read<AuthProvider>();
|
||||||
|
|
||||||
// Only sync if the user is logged in and the session isn't expired
|
|
||||||
if (authProvider.isLoggedIn && !authProvider.isSessionExpired) {
|
if (authProvider.isLoggedIn && !authProvider.isSessionExpired) {
|
||||||
debugPrint("[Main] User is logged in. Starting periodic data sync...");
|
debugPrint("[Main] User is logged in. Starting periodic data sync...");
|
||||||
|
|
||||||
// Run syncAllData but don't block. Catch errors to prevent the timer from stopping.
|
|
||||||
authProvider.syncAllData().catchError((e) {
|
authProvider.syncAllData().catchError((e) {
|
||||||
debugPrint("[Main] Error during periodic 1-hour sync: $e");
|
debugPrint("[Main] Error during periodic 1-hour sync: $e");
|
||||||
});
|
});
|
||||||
@ -327,7 +293,6 @@ class _RootAppState extends State<RootApp> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// --- END: MODIFICATION ---
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -348,7 +313,6 @@ class _RootAppState extends State<RootApp> {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
home: homeWidget,
|
home: homeWidget,
|
||||||
onGenerateRoute: (settings) {
|
onGenerateRoute: (settings) {
|
||||||
// Keep existing onGenerateRoute logic for Tarball
|
|
||||||
if (settings.name == '/marine/manual/tarball/step2') {
|
if (settings.name == '/marine/manual/tarball/step2') {
|
||||||
final args = settings.arguments as TarballSamplingData;
|
final args = settings.arguments as TarballSamplingData;
|
||||||
return MaterialPageRoute(builder: (context) {
|
return MaterialPageRoute(builder: (context) {
|
||||||
@ -366,126 +330,74 @@ class _RootAppState extends State<RootApp> {
|
|||||||
return const marineManualDataStatusLog.MarineManualDataStatusLog();
|
return const marineManualDataStatusLog.MarineManualDataStatusLog();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Add other potential dynamic routes here if necessary
|
return null;
|
||||||
return null; // Let routes map handle named routes
|
|
||||||
},
|
},
|
||||||
routes: {
|
routes: {
|
||||||
// Auth Routes
|
|
||||||
'/register': (context) => const RegisterScreen(),
|
'/register': (context) => const RegisterScreen(),
|
||||||
'/forgot-password': (context) => ForgotPasswordScreen(),
|
'/forgot-password': (context) => ForgotPasswordScreen(),
|
||||||
'/logout': (context) => const LogoutScreen(),
|
'/logout': (context) => const LogoutScreen(),
|
||||||
'/home': (context) => const HomePage(),
|
'/home': (context) => const HomePage(),
|
||||||
'/profile': (context) => const ProfileScreen(),
|
'/profile': (context) => const ProfileScreen(),
|
||||||
'/settings': (context) => const SettingsScreen(),
|
'/settings': (context) => const SettingsScreen(),
|
||||||
|
'/settings/submission-prefs': (context) => SubmissionPreferencesSettingsScreen(),
|
||||||
// --- START: New Settings Routes (const removed) ---
|
'/settings/telegram-alerts': (context) => TelegramAlertSettingsScreen(),
|
||||||
'/settings/submission-prefs': (context) =>
|
'/settings/api-ftp-configs': (context) => ApiFtpConfigurationsSettingsScreen(),
|
||||||
SubmissionPreferencesSettingsScreen(),
|
'/settings/parameter-limits': (context) => ParameterLimitsSettingsScreen(),
|
||||||
'/settings/telegram-alerts': (context) =>
|
'/settings/air-clients': (context) => AirClientsSettingsScreen(),
|
||||||
TelegramAlertSettingsScreen(),
|
'/settings/station-info': (context) => StationInfoSettingsScreen(),
|
||||||
'/settings/api-ftp-configs': (context) =>
|
|
||||||
ApiFtpConfigurationsSettingsScreen(),
|
|
||||||
'/settings/parameter-limits': (context) =>
|
|
||||||
ParameterLimitsSettingsScreen(),
|
|
||||||
'/settings/air-clients': (context) =>
|
|
||||||
AirClientsSettingsScreen(),
|
|
||||||
'/settings/station-info': (context) =>
|
|
||||||
StationInfoSettingsScreen(),
|
|
||||||
// --- END: New Settings Routes ---
|
|
||||||
|
|
||||||
// Department Home Pages
|
|
||||||
'/air/home': (context) => const AirHomePage(),
|
'/air/home': (context) => const AirHomePage(),
|
||||||
'/river/home': (context) => const RiverHomePage(),
|
'/river/home': (context) => const RiverHomePage(),
|
||||||
'/marine/home': (context) => const MarineHomePage(),
|
'/marine/home': (context) => const MarineHomePage(),
|
||||||
|
|
||||||
// Air Manual
|
|
||||||
'/air/manual/info': (context) => const AirManualInfoCentreDocument(),
|
'/air/manual/info': (context) => const AirManualInfoCentreDocument(),
|
||||||
'/air/manual/installation': (context) => const AirManualInstallationScreen(),
|
'/air/manual/installation': (context) => const AirManualInstallationScreen(),
|
||||||
'/air/manual/collection': (context) => const AirManualCollectionScreen(),
|
'/air/manual/collection': (context) => const AirManualCollectionScreen(),
|
||||||
'/air/manual/report': (context) => airManualReport.AirManualReport(),
|
'/air/manual/report': (context) => airManualReport.AirManualReport(),
|
||||||
'/air/manual/data-log': (context) => airManualDataStatusLog.AirManualDataStatusLog(),
|
'/air/manual/data-log': (context) => airManualDataStatusLog.AirManualDataStatusLog(),
|
||||||
'/air/manual/image-request': (context) => airManualImageRequest.AirManualImageRequest(),
|
'/air/manual/image-request': (context) => airManualImageRequest.AirManualImageRequest(),
|
||||||
|
|
||||||
// Air Continuous
|
|
||||||
'/air/continuous/info': (context) => const AirContinuousInfoCentreDocument(),
|
'/air/continuous/info': (context) => const AirContinuousInfoCentreDocument(),
|
||||||
'/air/continuous/overview': (context) => airContinuousOverview.OverviewScreen(),
|
'/air/continuous/overview': (context) => airContinuousOverview.OverviewScreen(),
|
||||||
'/air/continuous/entry': (context) => airContinuousEntry.EntryScreen(),
|
'/air/continuous/entry': (context) => airContinuousEntry.EntryScreen(),
|
||||||
'/air/continuous/report': (context) => airContinuousReport.ReportScreen(),
|
'/air/continuous/report': (context) => airContinuousReport.ReportScreen(),
|
||||||
|
|
||||||
// Air Investigative
|
|
||||||
'/air/investigative/info': (context) => const AirInvestigativeInfoCentreDocument(),
|
'/air/investigative/info': (context) => const AirInvestigativeInfoCentreDocument(),
|
||||||
'/air/investigative/overview': (context) => airInvestigativeOverview.OverviewScreen(),
|
'/air/investigative/overview': (context) => airInvestigativeOverview.OverviewScreen(),
|
||||||
'/air/investigative/entry': (context) => airInvestigativeEntry.EntryScreen(),
|
'/air/investigative/entry': (context) => airInvestigativeEntry.EntryScreen(),
|
||||||
'/air/investigative/report': (context) => airInvestigativeReport.ReportScreen(),
|
'/air/investigative/report': (context) => airInvestigativeReport.ReportScreen(),
|
||||||
|
|
||||||
// River Manual
|
|
||||||
'/river/manual/info': (context) => const RiverManualInfoCentreDocument(),
|
'/river/manual/info': (context) => const RiverManualInfoCentreDocument(),
|
||||||
'/river/manual/in-situ': (context) => riverManualInSituSampling.RiverInSituSamplingScreen(),
|
'/river/manual/in-situ': (context) => riverManualInSituSampling.RiverInSituSamplingScreen(),
|
||||||
'/river/manual/report': (context) => riverManualReport.RiverManualReport(),
|
'/river/manual/report': (context) => riverManualReport.RiverManualReport(),
|
||||||
'/river/manual/triennial': (context) =>
|
'/river/manual/triennial': (context) => riverManualTriennialSampling.RiverManualTriennialSamplingScreen(),
|
||||||
riverManualTriennialSampling.RiverManualTriennialSamplingScreen(),
|
|
||||||
'/river/manual/data-log': (context) => riverManualDataStatusLog.RiverManualDataStatusLog(),
|
'/river/manual/data-log': (context) => riverManualDataStatusLog.RiverManualDataStatusLog(),
|
||||||
'/river/manual/image-request': (context) => riverManualImageRequest.RiverManualImageRequest(),
|
'/river/manual/image-request': (context) => riverManualImageRequest.RiverManualImageRequest(),
|
||||||
|
|
||||||
// River Continuous
|
|
||||||
'/river/continuous/info': (context) => const RiverContinuousInfoCentreDocument(),
|
'/river/continuous/info': (context) => const RiverContinuousInfoCentreDocument(),
|
||||||
'/river/continuous/overview': (context) => riverContinuousOverview.OverviewScreen(),
|
'/river/continuous/overview': (context) => riverContinuousOverview.OverviewScreen(),
|
||||||
'/river/continuous/entry': (context) => riverContinuousEntry.EntryScreen(),
|
'/river/continuous/entry': (context) => riverContinuousEntry.EntryScreen(),
|
||||||
'/river/continuous/report': (context) => riverContinuousReport.ReportScreen(),
|
'/river/continuous/report': (context) => riverContinuousReport.ReportScreen(),
|
||||||
|
|
||||||
// River Investigative
|
|
||||||
'/river/investigative/info': (context) => const RiverInvestigativeInfoCentreDocument(),
|
'/river/investigative/info': (context) => const RiverInvestigativeInfoCentreDocument(),
|
||||||
// *** ADDED: Route for River Investigative Manual Sampling ***
|
'/river/investigative/manual-sampling': (context) => riverInvestigativeManualSampling.RiverInvestigativeManualSamplingScreen(),
|
||||||
'/river/investigative/manual-sampling': (context) =>
|
'/river/investigative/data-log': (context) => const riverInvestigativeDataStatusLog.RiverInvestigativeDataStatusLog(),
|
||||||
riverInvestigativeManualSampling.RiverInvestigativeManualSamplingScreen(),
|
'/river/investigative/image-request': (context) => const riverInvestigativeImageRequest.RiverInvestigativeImageRequest(),
|
||||||
// *** START: ADDED NEW RIVER INVESTIGATIVE ROUTES ***
|
'/river/investigative/overview': (context) => riverInvestigativeOverview.OverviewScreen(),
|
||||||
'/river/investigative/data-log': (context) =>
|
'/river/investigative/entry': (context) => riverInvestigativeEntry.EntryScreen(),
|
||||||
const riverInvestigativeDataStatusLog.RiverInvestigativeDataStatusLog(),
|
'/river/investigative/report': (context) => riverInvestigativeReport.ReportScreen(),
|
||||||
'/river/investigative/image-request': (context) =>
|
|
||||||
const riverInvestigativeImageRequest.RiverInvestigativeImageRequest(),
|
|
||||||
// *** END: ADDED NEW RIVER INVESTIGATIVE ROUTES ***
|
|
||||||
'/river/investigative/overview': (context) =>
|
|
||||||
riverInvestigativeOverview.OverviewScreen(), // Keep placeholder/future routes
|
|
||||||
'/river/investigative/entry': (context) =>
|
|
||||||
riverInvestigativeEntry.EntryScreen(), // Keep placeholder/future routes
|
|
||||||
'/river/investigative/report': (context) =>
|
|
||||||
riverInvestigativeReport.ReportScreen(), // Keep placeholder/future routes
|
|
||||||
|
|
||||||
// Marine Manual
|
|
||||||
'/marine/manual/info': (context) => marineManualInfoCentreDocument.MarineInfoCentreDocument(),
|
'/marine/manual/info': (context) => marineManualInfoCentreDocument.MarineInfoCentreDocument(),
|
||||||
'/marine/manual/pre-sampling': (context) => marineManualPreSampling.MarineManualPreSampling(),
|
'/marine/manual/pre-sampling': (context) => marineManualPreSampling.MarineManualPreSampling(),
|
||||||
'/marine/manual/in-situ': (context) => marineManualInSituSampling.MarineInSituSampling(),
|
'/marine/manual/in-situ': (context) => marineManualInSituSampling.MarineInSituSampling(),
|
||||||
'/marine/manual/tarball': (context) => const TarballSamplingStep1(),
|
'/marine/manual/tarball': (context) => const TarballSamplingStep1(),
|
||||||
'/marine/manual/report': (context) => const marineManualReport.MarineManualReportHomePage(),
|
'/marine/manual/report': (context) => const marineManualReport.MarineManualReportHomePage(),
|
||||||
'/marine/manual/report/npe': (context) => const MarineManualNPEReportHub(),
|
'/marine/manual/report/npe': (context) => const MarineManualNPEReportHub(),
|
||||||
'/marine/manual/report/pre-departure': (context) =>
|
'/marine/manual/report/pre-departure': (context) => const marineManualPreDepartureChecklist.MarineManualPreDepartureChecklistScreen(),
|
||||||
const marineManualPreDepartureChecklist.MarineManualPreDepartureChecklistScreen(),
|
'/marine/manual/report/calibration': (context) => const marineManualSondeCalibration.MarineManualSondeCalibrationScreen(),
|
||||||
'/marine/manual/report/calibration': (context) =>
|
'/marine/manual/report/maintenance': (context) => const marineManualEquipmentMaintenance.MarineManualEquipmentMaintenanceScreen(),
|
||||||
const marineManualSondeCalibration.MarineManualSondeCalibrationScreen(),
|
|
||||||
'/marine/manual/report/maintenance': (context) =>
|
|
||||||
const marineManualEquipmentMaintenance.MarineManualEquipmentMaintenanceScreen(),
|
|
||||||
'/marine/manual/image-request': (context) => const marineManualImageRequest.MarineImageRequestScreen(),
|
'/marine/manual/image-request': (context) => const marineManualImageRequest.MarineImageRequestScreen(),
|
||||||
// *** START: ADDED NEW ROUTE ***
|
'/marine/manual/report-log': (context) => const marineManualReportStatusLog.MarineManualReportStatusLog(),
|
||||||
'/marine/manual/report-log': (context) =>
|
|
||||||
const marineManualReportStatusLog.MarineManualReportStatusLog(),
|
|
||||||
// *** END: ADDED NEW ROUTE ***
|
|
||||||
|
|
||||||
// Marine Continuous
|
|
||||||
'/marine/continuous/info': (context) => const MarineContinuousInfoCentreDocument(),
|
'/marine/continuous/info': (context) => const MarineContinuousInfoCentreDocument(),
|
||||||
'/marine/continuous/overview': (context) => marineContinuousOverview.OverviewScreen(),
|
'/marine/continuous/overview': (context) => marineContinuousOverview.OverviewScreen(),
|
||||||
'/marine/continuous/entry': (context) => marineContinuousEntry.EntryScreen(),
|
'/marine/continuous/entry': (context) => marineContinuousEntry.EntryScreen(),
|
||||||
'/marine/continuous/report': (context) => marineContinuousReport.ReportScreen(),
|
'/marine/continuous/report': (context) => marineContinuousReport.ReportScreen(),
|
||||||
|
|
||||||
// Marine Investigative
|
|
||||||
'/marine/investigative/info': (context) => const MarineInvestigativeInfoCentreDocument(),
|
'/marine/investigative/info': (context) => const MarineInvestigativeInfoCentreDocument(),
|
||||||
'/marine/investigative/manual-sampling': (context) =>
|
'/marine/investigative/manual-sampling': (context) => marineInvestigativeManualSampling.MarineInvestigativeManualSampling(),
|
||||||
marineInvestigativeManualSampling.MarineInvestigativeManualSampling(),
|
'/marine/investigative/data-log': (context) => const marineInvestigativeDataStatusLog.MarineInvestigativeDataStatusLog(),
|
||||||
// *** START: ADDED NEW MARINE INVESTIGATIVE ROUTES ***
|
'/marine/investigative/image-request': (context) => const marineInvestigativeImageRequest.MarineInvestigativeImageRequestScreen(),
|
||||||
'/marine/investigative/data-log': (context) =>
|
|
||||||
const marineInvestigativeDataStatusLog.MarineInvestigativeDataStatusLog(),
|
|
||||||
'/marine/investigative/image-request': (context) =>
|
|
||||||
const marineInvestigativeImageRequest.MarineInvestigativeImageRequestScreen(),
|
|
||||||
// *** END: ADDED NEW MARINE INVESTIGATIVE ROUTES ***
|
|
||||||
'/marine/investigative/overview': (context) => marineInvestigativeOverview.OverviewScreen(),
|
'/marine/investigative/overview': (context) => marineInvestigativeOverview.OverviewScreen(),
|
||||||
'/marine/investigative/entry': (context) => marineInvestigativeEntry.EntryScreen(),
|
'/marine/investigative/entry': (context) => marineInvestigativeEntry.EntryScreen(),
|
||||||
'/marine/investigative/report': (context) => marineInvestigativeReport.ReportScreen(),
|
'/marine/investigative/report': (context) => marineInvestigativeReport.ReportScreen(),
|
||||||
@ -506,50 +418,33 @@ class SessionAwareWrapper extends StatefulWidget {
|
|||||||
|
|
||||||
class _SessionAwareWrapperState extends State<SessionAwareWrapper> {
|
class _SessionAwareWrapperState extends State<SessionAwareWrapper> {
|
||||||
bool _isDialogShowing = false;
|
bool _isDialogShowing = false;
|
||||||
// --- MODIFICATION START ---
|
|
||||||
// 1. Create a variable to hold the AuthProvider instance.
|
|
||||||
late AuthProvider _authProvider;
|
late AuthProvider _authProvider;
|
||||||
// --- MODIFICATION END ---
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
// --- MODIFICATION START ---
|
|
||||||
// 2. Get the provider reference here and add the listener.
|
|
||||||
_authProvider = Provider.of<AuthProvider>(context);
|
_authProvider = Provider.of<AuthProvider>(context);
|
||||||
_authProvider.addListener(_handleSessionExpired);
|
_authProvider.addListener(_handleSessionExpired);
|
||||||
// --- MODIFICATION END ---
|
|
||||||
|
|
||||||
// Call initial check here if needed, or rely on RootApp's check.
|
|
||||||
_checkAndShowDialogIfNeeded(_authProvider.isSessionExpired);
|
_checkAndShowDialogIfNeeded(_authProvider.isSessionExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// --- MODIFICATION START ---
|
|
||||||
// 3. Use the saved reference to remove the listener. This is safe.
|
|
||||||
_authProvider.removeListener(_handleSessionExpired);
|
_authProvider.removeListener(_handleSessionExpired);
|
||||||
// --- MODIFICATION END ---
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSessionExpired() {
|
void _handleSessionExpired() {
|
||||||
// --- MODIFICATION START ---
|
|
||||||
// 4. Use the saved _authProvider reference.
|
|
||||||
_checkAndShowDialogIfNeeded(_authProvider.isSessionExpired);
|
_checkAndShowDialogIfNeeded(_authProvider.isSessionExpired);
|
||||||
// --- MODIFICATION END ---
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkAndShowDialogIfNeeded(bool isExpired) {
|
void _checkAndShowDialogIfNeeded(bool isExpired) {
|
||||||
if (isExpired && !_isDialogShowing && mounted) {
|
if (isExpired && !_isDialogShowing && mounted) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted && !_isDialogShowing) { // Double check mounted and flag
|
if (mounted && !_isDialogShowing) {
|
||||||
_showSessionExpiredDialog();
|
_showSessionExpiredDialog();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (!isExpired && _isDialogShowing && mounted) {
|
|
||||||
// If session becomes valid again and dialog is showing, maybe dismiss it?
|
|
||||||
// Or rely on user action. For now, we only trigger ON expiry.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,7 +456,6 @@ class _SessionAwareWrapperState extends State<SessionAwareWrapper> {
|
|||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext dialogContext) {
|
builder: (BuildContext dialogContext) {
|
||||||
// Use the state's _authProvider reference, which is safe.
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text("Session Expired"),
|
title: const Text("Session Expired"),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
@ -571,25 +465,19 @@ class _SessionAwareWrapperState extends State<SessionAwareWrapper> {
|
|||||||
child: const Text("Continue Offline"),
|
child: const Text("Continue Offline"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
// Optionally: _authProvider.clearSessionExpiredFlag(); // If needed
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: const Text("Login Now"),
|
child: const Text("Login Now"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// --- MODIFICATION START ---
|
|
||||||
// 5. Use the saved reference to log out.
|
|
||||||
_authProvider.logout();
|
_authProvider.logout();
|
||||||
// --- MODIFICATION END ---
|
Navigator.of(dialogContext).pop();
|
||||||
Navigator.of(dialogContext).pop(); // Close dialog first
|
|
||||||
// RootApp builder will handle navigation to LoginScreen
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Reset flag after dialog is dismissed
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isDialogShowing = false);
|
setState(() => _isDialogShowing = false);
|
||||||
}
|
}
|
||||||
@ -612,7 +500,7 @@ class SplashScreen extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Image.asset(
|
||||||
'assets/icon4.png', // Ensure this asset exists
|
'assets/icon4.png',
|
||||||
height: 360,
|
height: 360,
|
||||||
width: 360,
|
width: 360,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,6 +11,9 @@ class MarineManualNpeReportData {
|
|||||||
String? eventDate;
|
String? eventDate;
|
||||||
String? eventTime;
|
String? eventTime;
|
||||||
|
|
||||||
|
// --- Source Origin (Kept for internal tracking, but not displayed) ---
|
||||||
|
String? sourceOrigin;
|
||||||
|
|
||||||
// --- Location Info ---
|
// --- Location Info ---
|
||||||
String? locationDescription; // For new locations
|
String? locationDescription; // For new locations
|
||||||
String? stateName; // For new locations or tarball stations
|
String? stateName; // For new locations or tarball stations
|
||||||
@ -52,10 +55,9 @@ class MarineManualNpeReportData {
|
|||||||
String? image3Remark;
|
String? image3Remark;
|
||||||
String? image4Remark;
|
String? image4Remark;
|
||||||
|
|
||||||
// --- START: Added Tarball Classification Fields ---
|
// --- Tarball Classification Fields ---
|
||||||
int? tarballClassificationId;
|
int? tarballClassificationId;
|
||||||
Map<String, dynamic>? selectedTarballClassification;
|
Map<String, dynamic>? selectedTarballClassification;
|
||||||
// --- END: Added Tarball Classification Fields ---
|
|
||||||
|
|
||||||
// --- Submission Status ---
|
// --- Submission Status ---
|
||||||
String? submissionStatus;
|
String? submissionStatus;
|
||||||
@ -71,6 +73,7 @@ class MarineManualNpeReportData {
|
|||||||
'firstSamplerUserId': firstSamplerUserId,
|
'firstSamplerUserId': firstSamplerUserId,
|
||||||
'eventDate': eventDate,
|
'eventDate': eventDate,
|
||||||
'eventTime': eventTime,
|
'eventTime': eventTime,
|
||||||
|
'sourceOrigin': sourceOrigin,
|
||||||
'locationDescription': locationDescription,
|
'locationDescription': locationDescription,
|
||||||
'stateName': stateName,
|
'stateName': stateName,
|
||||||
'selectedStation': selectedStation,
|
'selectedStation': selectedStation,
|
||||||
@ -89,10 +92,8 @@ class MarineManualNpeReportData {
|
|||||||
'image2Remark': image2Remark,
|
'image2Remark': image2Remark,
|
||||||
'image3Remark': image3Remark,
|
'image3Remark': image3Remark,
|
||||||
'image4Remark': image4Remark,
|
'image4Remark': image4Remark,
|
||||||
// --- Added Fields ---
|
|
||||||
'tarballClassificationId': tarballClassificationId,
|
'tarballClassificationId': tarballClassificationId,
|
||||||
'selectedTarballClassification': selectedTarballClassification,
|
'selectedTarballClassification': selectedTarballClassification,
|
||||||
// ---
|
|
||||||
'submissionStatus': submissionStatus,
|
'submissionStatus': submissionStatus,
|
||||||
'submissionMessage': submissionMessage,
|
'submissionMessage': submissionMessage,
|
||||||
'reportId': reportId,
|
'reportId': reportId,
|
||||||
@ -111,6 +112,9 @@ class MarineManualNpeReportData {
|
|||||||
add('npe_date', eventDate);
|
add('npe_date', eventDate);
|
||||||
add('npe_time', eventTime);
|
add('npe_time', eventTime);
|
||||||
add('first_sampler_user_id', firstSamplerUserId);
|
add('first_sampler_user_id', firstSamplerUserId);
|
||||||
|
|
||||||
|
// add('npe_source_origin', sourceOrigin); // Disabled to prevent SQL error
|
||||||
|
|
||||||
if (selectedStation != null) {
|
if (selectedStation != null) {
|
||||||
add('station_id', selectedStation?['station_id'] ?? selectedStation?['tbl_station_id']);
|
add('station_id', selectedStation?['station_id'] ?? selectedStation?['tbl_station_id']);
|
||||||
add('station_code', selectedStation?['man_station_code'] ?? selectedStation?['tbl_station_code']);
|
add('station_code', selectedStation?['man_station_code'] ?? selectedStation?['tbl_station_code']);
|
||||||
@ -143,9 +147,7 @@ class MarineManualNpeReportData {
|
|||||||
add('npe_image_3_remarks', image3Remark);
|
add('npe_image_3_remarks', image3Remark);
|
||||||
add('npe_image_4_remarks', image4Remark);
|
add('npe_image_4_remarks', image4Remark);
|
||||||
|
|
||||||
// --- Added Fields ---
|
|
||||||
add('classification_id', tarballClassificationId);
|
add('classification_id', tarballClassificationId);
|
||||||
// ---
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@ -162,16 +164,24 @@ class MarineManualNpeReportData {
|
|||||||
|
|
||||||
/// Generates the Telegram alert message for this NPE report.
|
/// Generates the Telegram alert message for this NPE report.
|
||||||
String generateTelegramAlertMessage() {
|
String generateTelegramAlertMessage() {
|
||||||
final locationDesc = selectedStation != null
|
String locationDesc;
|
||||||
? '${selectedStation!['man_station_name'] ?? selectedStation!['tbl_station_name']}'
|
|
||||||
: locationDescription ?? 'A custom location';
|
// --- START: MODIFIED to include Station Code + Name ---
|
||||||
|
if (selectedStation != null) {
|
||||||
|
final code = selectedStation!['man_station_code'] ?? selectedStation!['tbl_station_code'] ?? 'N/A';
|
||||||
|
final name = selectedStation!['man_station_name'] ?? selectedStation!['tbl_station_name'] ?? 'N/A';
|
||||||
|
locationDesc = '$code - $name';
|
||||||
|
} else {
|
||||||
|
locationDesc = locationDescription ?? 'A custom location';
|
||||||
|
}
|
||||||
|
// --- END: MODIFIED ---
|
||||||
|
|
||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln('🚨 *Notification of Pollution Event (NPE) Submitted:*')
|
..writeln('🚨 *Notification of Pollution Event (NPE) Submitted:*')
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln('*Location:* $locationDesc')
|
..writeln('*Location:* $locationDesc')
|
||||||
..writeln('*Event Date:* $eventDate $eventTime')
|
..writeln('*Event Date:* $eventDate $eventTime')
|
||||||
..writeln('*Submitted by:* $firstSamplerName')
|
..writeln('*Submitted by:* ${firstSamplerName ?? "N/A"}')
|
||||||
..writeln('*Status of Submission:* Successful');
|
..writeln('*Status of Submission:* Successful');
|
||||||
|
|
||||||
final observations = fieldObservations.entries
|
final observations = fieldObservations.entries
|
||||||
@ -195,13 +205,11 @@ class MarineManualNpeReportData {
|
|||||||
..writeln('*Possible Source:* $possibleSource');
|
..writeln('*Possible Source:* $possibleSource');
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Added Tarball Classification to Telegram message ---
|
|
||||||
if (selectedTarballClassification != null) {
|
if (selectedTarballClassification != null) {
|
||||||
buffer
|
buffer
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln('*Tarball Classification:* ${selectedTarballClassification!['classification_name']}');
|
..writeln('*Tarball Classification:* ${selectedTarballClassification!['classification_name']}');
|
||||||
}
|
}
|
||||||
// ---
|
|
||||||
|
|
||||||
final remarks = [
|
final remarks = [
|
||||||
if (image1Remark != null && image1Remark!.isNotEmpty) '*Fig 1:* $image1Remark',
|
if (image1Remark != null && image1Remark!.isNotEmpty) '*Fig 1:* $image1Remark',
|
||||||
|
|||||||
@ -334,4 +334,61 @@ class RiverManualTriennialSamplingData {
|
|||||||
//data.removeWhere((key, value) => value == null);
|
//data.removeWhere((key, value) => value == null);
|
||||||
return jsonEncode(data);
|
return jsonEncode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a JSON object for basic form info, mimicking 'river_triennial_basic_form.json'.
|
||||||
|
String toBasicFormJson() {
|
||||||
|
final data = {
|
||||||
|
// --- START FIX: Map model properties to correct form keys ---
|
||||||
|
'tech_name': firstSamplerName,
|
||||||
|
'sampler_2ndname': secondSampler?['first_name'],
|
||||||
|
'sample_date': samplingDate,
|
||||||
|
'sample_time': samplingTime,
|
||||||
|
'sampling_type': samplingType,
|
||||||
|
'sample_state': selectedStateName,
|
||||||
|
'station_id': selectedStation?['sampling_station_code'],
|
||||||
|
'station_latitude': stationLatitude,
|
||||||
|
'station_longitude': stationLongitude,
|
||||||
|
'latitude': currentLatitude, // Current location lat
|
||||||
|
'longitude': currentLongitude, // Current location lon
|
||||||
|
'sample_id': sampleIdCode,
|
||||||
|
// --- END FIX ---
|
||||||
|
};
|
||||||
|
data.removeWhere((key, value) => value == null);
|
||||||
|
return jsonEncode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a JSON object for sensor readings, mimicking 'river_sampling_reading.json'.
|
||||||
|
String toReadingJson() {
|
||||||
|
final data = {
|
||||||
|
// --- START FIX: Map model properties to correct reading keys ---
|
||||||
|
'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration,
|
||||||
|
'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation,
|
||||||
|
'ph': ph == -999.0 ? null : ph,
|
||||||
|
'salinity': salinity == -999.0 ? null : salinity,
|
||||||
|
'temperature': temperature == -999.0 ? null : temperature,
|
||||||
|
'turbidity': turbidity == -999.0 ? null : turbidity,
|
||||||
|
'tds': tds == -999.0 ? null : tds,
|
||||||
|
'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity,
|
||||||
|
'ammonia': ammonia == -999.0 ? null : ammonia,
|
||||||
|
'flowrate': flowrateValue,
|
||||||
|
'date_sampling_reading': dataCaptureDate, // Use data capture date/time
|
||||||
|
'time_sampling_reading': dataCaptureTime, // Use data capture date/time
|
||||||
|
// --- END FIX ---
|
||||||
|
};
|
||||||
|
data.removeWhere((key, value) => value == null);
|
||||||
|
return jsonEncode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a JSON object for manual info, mimicking 'river_manual_info.json'.
|
||||||
|
String toManualInfoJson() {
|
||||||
|
final data = {
|
||||||
|
// --- START FIX: Map model properties to correct manual info keys ---
|
||||||
|
'weather': weather,
|
||||||
|
'remarks_event': eventRemarks,
|
||||||
|
'remarks_lab': labRemarks,
|
||||||
|
// --- END FIX ---
|
||||||
|
};
|
||||||
|
data.removeWhere((key, value) => value == null);
|
||||||
|
return jsonEncode(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ class SubmissionLogEntry {
|
|||||||
final String type;
|
final String type;
|
||||||
final String title;
|
final String title;
|
||||||
final String stationCode;
|
final String stationCode;
|
||||||
final String senderName; // <-- ADDED
|
final String senderName;
|
||||||
final DateTime submissionDateTime;
|
final DateTime submissionDateTime;
|
||||||
final String? reportId;
|
final String? reportId;
|
||||||
final String status;
|
final String status;
|
||||||
@ -51,7 +51,7 @@ class SubmissionLogEntry {
|
|||||||
required this.type,
|
required this.type,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.stationCode,
|
required this.stationCode,
|
||||||
required this.senderName, // <-- ADDED
|
required this.senderName,
|
||||||
required this.submissionDateTime,
|
required this.submissionDateTime,
|
||||||
this.reportId,
|
this.reportId,
|
||||||
required this.status,
|
required this.status,
|
||||||
@ -97,7 +97,8 @@ class _MarineManualReportStatusLogState
|
|||||||
// --- START: COPIED FROM SCREEN FILE ---
|
// --- START: COPIED FROM SCREEN FILE ---
|
||||||
// This is the "single source of truth" for categories
|
// This is the "single source of truth" for categories
|
||||||
final Map<String, List<String>> _checklistSections = {
|
final Map<String, List<String>> _checklistSections = {
|
||||||
'INTERNAL - IN-SITU SAMPLING': [ // Section title matches PDF
|
'INTERNAL - IN-SITU SAMPLING': [
|
||||||
|
// Section title matches PDF
|
||||||
'Marine manual Standard Operation Procedure (SOP)', // Item text matches PDF
|
'Marine manual Standard Operation Procedure (SOP)', // Item text matches PDF
|
||||||
'Back-up Sampling Sheet & Chain of Custody form', // Item text matches PDF
|
'Back-up Sampling Sheet & Chain of Custody form', // Item text matches PDF
|
||||||
'Calibration worksheet', // Item text matches PDF
|
'Calibration worksheet', // Item text matches PDF
|
||||||
@ -125,13 +126,15 @@ class _MarineManualReportStatusLogState
|
|||||||
'Raincoat/Poncho', // Item text matches PDF
|
'Raincoat/Poncho', // Item text matches PDF
|
||||||
'Ice packets', // Item text matches PDF
|
'Ice packets', // Item text matches PDF
|
||||||
],
|
],
|
||||||
'INTERNAL-TARBALL SAMPLING': [ // Section title matches PDF
|
'INTERNAL-TARBALL SAMPLING': [
|
||||||
|
// Section title matches PDF
|
||||||
'Measuring tape (100 meter)', // Item text matches PDF
|
'Measuring tape (100 meter)', // Item text matches PDF
|
||||||
'Steel raking', // Item text matches PDF
|
'Steel raking', // Item text matches PDF
|
||||||
'Aluminum foil', // Item text matches PDF
|
'Aluminum foil', // Item text matches PDF
|
||||||
'Zipper bags', // Item text matches PDF
|
'Zipper bags', // Item text matches PDF
|
||||||
],
|
],
|
||||||
'EXTERNAL - LABORATORY': [ // Section title matches PDF
|
'EXTERNAL - LABORATORY': [
|
||||||
|
// Section title matches PDF
|
||||||
'Sufficient sets of cooler box and sampling bottles with label', // Item text matches PDF
|
'Sufficient sets of cooler box and sampling bottles with label', // Item text matches PDF
|
||||||
'Field duplicate sampling bottles (if any)', // Item text matches PDF
|
'Field duplicate sampling bottles (if any)', // Item text matches PDF
|
||||||
'Blank samples sampling bottles (if any)', // Item text matches PDF
|
'Blank samples sampling bottles (if any)', // Item text matches PDF
|
||||||
@ -152,14 +155,18 @@ class _MarineManualReportStatusLogState
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
// Initialize all required services from Provider
|
// Initialize all required services from Provider
|
||||||
_localStorageService = Provider.of<LocalStorageService>(context, listen: false); // Added listen: false
|
_localStorageService =
|
||||||
_npeReportService = Provider.of<MarineNpeReportService>(context, listen: false); // Added listen: false
|
Provider.of<LocalStorageService>(context, listen: false);
|
||||||
_preDepartureService =
|
_npeReportService =
|
||||||
Provider.of<MarineManualPreDepartureService>(context, listen: false); // Added listen: false
|
Provider.of<MarineNpeReportService>(context, listen: false);
|
||||||
_sondeCalibrationService =
|
_preDepartureService = Provider.of<MarineManualPreDepartureService>(context,
|
||||||
Provider.of<MarineManualSondeCalibrationService>(context, listen: false); // Added listen: false
|
listen: false);
|
||||||
|
_sondeCalibrationService = Provider.of<MarineManualSondeCalibrationService>(
|
||||||
|
context,
|
||||||
|
listen: false);
|
||||||
_equipmentMaintenanceService =
|
_equipmentMaintenanceService =
|
||||||
Provider.of<MarineManualEquipmentMaintenanceService>(context, listen: false); // Added listen: false
|
Provider.of<MarineManualEquipmentMaintenanceService>(context,
|
||||||
|
listen: false);
|
||||||
|
|
||||||
// Load logs after services are initialized
|
// Load logs after services are initialized
|
||||||
if (_isLoading) {
|
if (_isLoading) {
|
||||||
@ -185,9 +192,7 @@ class _MarineManualReportStatusLogState
|
|||||||
await _localStorageService.getAllSondeCalibrationLogs();
|
await _localStorageService.getAllSondeCalibrationLogs();
|
||||||
final equipmentMaintenanceLogs =
|
final equipmentMaintenanceLogs =
|
||||||
await _localStorageService.getAllEquipmentMaintenanceLogs();
|
await _localStorageService.getAllEquipmentMaintenanceLogs();
|
||||||
// *** START: Fixed method name ***
|
|
||||||
final npeReportLogs = await _localStorageService.getAllNpeLogs();
|
final npeReportLogs = await _localStorageService.getAllNpeLogs();
|
||||||
// *** END: Fixed method name ***
|
|
||||||
|
|
||||||
final List<SubmissionLogEntry> tempPreSampling = [];
|
final List<SubmissionLogEntry> tempPreSampling = [];
|
||||||
final List<SubmissionLogEntry> tempReport = [];
|
final List<SubmissionLogEntry> tempReport = [];
|
||||||
@ -199,9 +204,7 @@ class _MarineManualReportStatusLogState
|
|||||||
type: 'Pre-Departure Checklist',
|
type: 'Pre-Departure Checklist',
|
||||||
title: 'Pre-Departure Checklist',
|
title: 'Pre-Departure Checklist',
|
||||||
stationCode: 'N/A',
|
stationCode: 'N/A',
|
||||||
// --- START: MODIFIED ---
|
|
||||||
senderName: (log['reporterName'] as String?) ?? 'Unknown User',
|
senderName: (log['reporterName'] as String?) ?? 'Unknown User',
|
||||||
// --- END: MODIFIED ---
|
|
||||||
submissionDateTime: DateTime.tryParse(dateStr) ??
|
submissionDateTime: DateTime.tryParse(dateStr) ??
|
||||||
DateTime.fromMillisecondsSinceEpoch(0),
|
DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
@ -215,18 +218,12 @@ class _MarineManualReportStatusLogState
|
|||||||
|
|
||||||
// 2. Process Sonde Calibration Logs -> Pre-Sampling
|
// 2. Process Sonde Calibration Logs -> Pre-Sampling
|
||||||
for (var log in sondeCalibrationLogs) {
|
for (var log in sondeCalibrationLogs) {
|
||||||
// --- START: MODIFIED ---
|
|
||||||
final dateStr = log['startDateTime'] ?? '';
|
final dateStr = log['startDateTime'] ?? '';
|
||||||
// --- END: MODIFIED ---
|
|
||||||
tempPreSampling.add(SubmissionLogEntry(
|
tempPreSampling.add(SubmissionLogEntry(
|
||||||
type: 'Sonde Calibration',
|
type: 'Sonde Calibration',
|
||||||
// --- START: MODIFIED LINE ---
|
title: 'Sonde Calibration',
|
||||||
title: 'Sonde Calibration', // Use module name as title
|
|
||||||
// --- END: MODIFIED LINE ---
|
|
||||||
stationCode: log['location'] ?? 'N/A',
|
stationCode: log['location'] ?? 'N/A',
|
||||||
// --- START: MODIFIED ---
|
|
||||||
senderName: (log['calibratedByUserName'] as String?) ?? 'Unknown User',
|
senderName: (log['calibratedByUserName'] as String?) ?? 'Unknown User',
|
||||||
// --- END: MODIFIED ---
|
|
||||||
submissionDateTime: DateTime.tryParse(dateStr) ??
|
submissionDateTime: DateTime.tryParse(dateStr) ??
|
||||||
DateTime.fromMillisecondsSinceEpoch(0),
|
DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
@ -240,16 +237,12 @@ class _MarineManualReportStatusLogState
|
|||||||
|
|
||||||
// 3. Process Equipment Maintenance Logs -> Pre-Sampling
|
// 3. Process Equipment Maintenance Logs -> Pre-Sampling
|
||||||
for (var log in equipmentMaintenanceLogs) {
|
for (var log in equipmentMaintenanceLogs) {
|
||||||
// --- START: MODIFIED ---
|
|
||||||
final dateStr = log['maintenanceDate'] ?? '';
|
final dateStr = log['maintenanceDate'] ?? '';
|
||||||
// --- END: MODIFIED ---
|
|
||||||
tempPreSampling.add(SubmissionLogEntry(
|
tempPreSampling.add(SubmissionLogEntry(
|
||||||
type: 'Equipment Maintenance',
|
type: 'Equipment Maintenance',
|
||||||
title: 'Equipment Maintenance',
|
title: 'Equipment Maintenance',
|
||||||
stationCode: log['location'] ?? 'N/A',
|
stationCode: log['location'] ?? 'N/A',
|
||||||
// --- START: MODIFIED ---
|
|
||||||
senderName: (log['conductedByUserName'] as String?) ?? 'Unknown User',
|
senderName: (log['conductedByUserName'] as String?) ?? 'Unknown User',
|
||||||
// --- END: MODIFIED ---
|
|
||||||
submissionDateTime: DateTime.tryParse(dateStr) ??
|
submissionDateTime: DateTime.tryParse(dateStr) ??
|
||||||
DateTime.fromMillisecondsSinceEpoch(0),
|
DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
@ -277,9 +270,7 @@ class _MarineManualReportStatusLogState
|
|||||||
type: 'NPE Report',
|
type: 'NPE Report',
|
||||||
title: title,
|
title: title,
|
||||||
stationCode: stationCode,
|
stationCode: stationCode,
|
||||||
// --- START: MODIFIED FOR NULL SAFETY ---
|
senderName: (log['firstSamplerName'] as String?) ?? 'Unknown User',
|
||||||
senderName: (log['firstSamplerName'] as String?) ?? 'Unknown User', // <-- FIXED
|
|
||||||
// --- END: MODIFIED FOR NULL SAFETY ---
|
|
||||||
submissionDateTime: DateTime.tryParse('$dateStr $timeStr') ??
|
submissionDateTime: DateTime.tryParse('$dateStr $timeStr') ??
|
||||||
DateTime.fromMillisecondsSinceEpoch(0),
|
DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
@ -288,14 +279,15 @@ class _MarineManualReportStatusLogState
|
|||||||
rawData: log,
|
rawData: log,
|
||||||
serverName: log['serverConfigName'] ?? 'Unknown Server',
|
serverName: log['serverConfigName'] ?? 'Unknown Server',
|
||||||
apiStatusRaw: log['api_status'],
|
apiStatusRaw: log['api_status'],
|
||||||
ftpStatusRaw: log['ftp_status'], // NPE has FTP
|
ftpStatusRaw: log['ftp_status'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort logs by date (descending)
|
// Sort logs by date (descending)
|
||||||
tempPreSampling
|
tempPreSampling
|
||||||
.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
||||||
tempReport.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
tempReport
|
||||||
|
.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -328,7 +320,7 @@ class _MarineManualReportStatusLogState
|
|||||||
log.stationCode.toLowerCase().contains(query) ||
|
log.stationCode.toLowerCase().contains(query) ||
|
||||||
log.serverName.toLowerCase().contains(query) ||
|
log.serverName.toLowerCase().contains(query) ||
|
||||||
log.type.toLowerCase().contains(query) ||
|
log.type.toLowerCase().contains(query) ||
|
||||||
log.senderName.toLowerCase().contains(query) || // <-- ADDED
|
log.senderName.toLowerCase().contains(query) ||
|
||||||
(log.reportId?.toLowerCase() ?? '').contains(query);
|
(log.reportId?.toLowerCase() ?? '').contains(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,23 +379,25 @@ class _MarineManualReportStatusLogState
|
|||||||
data.location = logData['location'];
|
data.location = logData['location'];
|
||||||
data.startDateTime = logData['startDateTime'];
|
data.startDateTime = logData['startDateTime'];
|
||||||
data.endDateTime = logData['endDateTime'];
|
data.endDateTime = logData['endDateTime'];
|
||||||
data.ph7Mv = (logData['ph_7_mv'] as num?)?.toDouble(); // Fixed key
|
data.ph7Mv = (logData['ph_7_mv'] as num?)?.toDouble();
|
||||||
data.ph7Before = (logData['ph_7_before'] as num?)?.toDouble(); // Fixed key
|
data.ph7Before = (logData['ph_7_before'] as num?)?.toDouble();
|
||||||
data.ph7After = (logData['ph_7_after'] as num?)?.toDouble(); // Fixed key
|
data.ph7After = (logData['ph_7_after'] as num?)?.toDouble();
|
||||||
data.ph10Mv = (logData['ph_10_mv'] as num?)?.toDouble(); // Fixed key
|
data.ph10Mv = (logData['ph_10_mv'] as num?)?.toDouble();
|
||||||
data.ph10Before = (logData['ph_10_before'] as num?)?.toDouble(); // Fixed key
|
data.ph10Before = (logData['ph_10_before'] as num?)?.toDouble();
|
||||||
data.ph10After = (logData['ph_10_after'] as num?)?.toDouble(); // Fixed key
|
data.ph10After = (logData['ph_10_after'] as num?)?.toDouble();
|
||||||
data.condBefore = (logData['cond_before'] as num?)?.toDouble(); // Fixed key
|
data.condBefore = (logData['cond_before'] as num?)?.toDouble();
|
||||||
data.condAfter = (logData['cond_after'] as num?)?.toDouble(); // Fixed key
|
data.condAfter = (logData['cond_after'] as num?)?.toDouble();
|
||||||
data.doBefore = (logData['do_before'] as num?)?.toDouble(); // Fixed key
|
data.doBefore = (logData['do_before'] as num?)?.toDouble();
|
||||||
data.doAfter = (logData['do_after'] as num?)?.toDouble(); // Fixed key
|
data.doAfter = (logData['do_after'] as num?)?.toDouble();
|
||||||
data.turbidity0Before = (logData['turbidity_0_before'] as num?)?.toDouble(); // Fixed key
|
data.turbidity0Before =
|
||||||
data.turbidity0After = (logData['turbidity_0_after'] as num?)?.toDouble(); // Fixed key
|
(logData['turbidity_0_before'] as num?)?.toDouble();
|
||||||
|
data.turbidity0After =
|
||||||
|
(logData['turbidity_0_after'] as num?)?.toDouble();
|
||||||
data.turbidity124Before =
|
data.turbidity124Before =
|
||||||
(logData['turbidity_124_before'] as num?)?.toDouble(); // Fixed key
|
(logData['turbidity_124_before'] as num?)?.toDouble();
|
||||||
data.turbidity124After =
|
data.turbidity124After =
|
||||||
(logData['turbidity_124_after'] as num?)?.toDouble(); // Fixed key
|
(logData['turbidity_124_after'] as num?)?.toDouble();
|
||||||
data.calibrationStatus = logData['calibration_status']; // Fixed key
|
data.calibrationStatus = logData['calibration_status'];
|
||||||
data.remarks = logData['remarks'];
|
data.remarks = logData['remarks'];
|
||||||
|
|
||||||
result = await _sondeCalibrationService.submitCalibration(
|
result = await _sondeCalibrationService.submitCalibration(
|
||||||
@ -461,9 +455,7 @@ class _MarineManualReportStatusLogState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** START: Fixed method name ***
|
|
||||||
result = await _equipmentMaintenanceService.submitMaintenanceReport(
|
result = await _equipmentMaintenanceService.submitMaintenanceReport(
|
||||||
// *** END: Fixed method name ***
|
|
||||||
data: data,
|
data: data,
|
||||||
authProvider: authProvider,
|
authProvider: authProvider,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
@ -483,7 +475,8 @@ class _MarineManualReportStatusLogState
|
|||||||
data.selectedStation = logData['selectedStation'];
|
data.selectedStation = logData['selectedStation'];
|
||||||
data.latitude = logData['latitude'];
|
data.latitude = logData['latitude'];
|
||||||
data.longitude = logData['longitude'];
|
data.longitude = logData['longitude'];
|
||||||
data.oxygenSaturation = (logData['oxygenSaturation'] as num?)?.toDouble();
|
data.oxygenSaturation =
|
||||||
|
(logData['oxygenSaturation'] as num?)?.toDouble();
|
||||||
data.electricalConductivity =
|
data.electricalConductivity =
|
||||||
(logData['electricalConductivity'] as num?)?.toDouble();
|
(logData['electricalConductivity'] as num?)?.toDouble();
|
||||||
data.oxygenConcentration =
|
data.oxygenConcentration =
|
||||||
@ -512,13 +505,11 @@ class _MarineManualReportStatusLogState
|
|||||||
data.image3 = _createFileFromPath(logData['image3Path']);
|
data.image3 = _createFileFromPath(logData['image3Path']);
|
||||||
data.image4 = _createFileFromPath(logData['image4Path']);
|
data.image4 = _createFileFromPath(logData['image4Path']);
|
||||||
|
|
||||||
// *** START: Removed extra parameters ***
|
|
||||||
result = await _npeReportService.submitNpeReport(
|
result = await _npeReportService.submitNpeReport(
|
||||||
data: data,
|
data: data,
|
||||||
authProvider: authProvider,
|
authProvider: authProvider,
|
||||||
logDirectory: log.rawData['logDirectory'] as String?,
|
logDirectory: log.rawData['logDirectory'] as String?,
|
||||||
);
|
);
|
||||||
// *** END: Removed extra parameters ***
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -528,8 +519,8 @@ class _MarineManualReportStatusLogState
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content:
|
||||||
result['message'] ?? 'Resubmission process completed.')),
|
Text(result['message'] ?? 'Resubmission process completed.')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -580,9 +571,7 @@ class _MarineManualReportStatusLogState
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a collapsible card for a category of logs.
|
/// Builds a collapsible card for a category of logs.
|
||||||
Widget _buildCategorySection(
|
Widget _buildCategorySection(String category, List<SubmissionLogEntry> logs,
|
||||||
String category,
|
|
||||||
List<SubmissionLogEntry> logs,
|
|
||||||
TextEditingController searchController) {
|
TextEditingController searchController) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
@ -621,7 +610,8 @@ class _MarineManualReportStatusLogState
|
|||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('No logs match your search in this category.')))
|
child:
|
||||||
|
Text('No logs match your search in this category.')))
|
||||||
else
|
else
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -681,13 +671,11 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- START: MODIFIED SUBTITLE ---
|
|
||||||
final bool isDateValid = !log.submissionDateTime
|
final bool isDateValid = !log.submissionDateTime
|
||||||
.isAtSameMomentAs(DateTime.fromMillisecondsSinceEpoch(0));
|
.isAtSameMomentAs(DateTime.fromMillisecondsSinceEpoch(0));
|
||||||
final subtitle = isDateValid
|
final subtitle = isDateValid
|
||||||
? '${log.senderName} - ${DateFormat('yyyy-MM-dd HH:mm').format(log.submissionDateTime)}'
|
? '${log.senderName} - ${DateFormat('yyyy-MM-dd HH:mm').format(log.submissionDateTime)}'
|
||||||
: '${log.senderName} - Invalid Date';
|
: '${log.senderName} - Invalid Date';
|
||||||
// --- END: MODIFIED SUBTITLE ---
|
|
||||||
|
|
||||||
return ExpansionTile(
|
return ExpansionTile(
|
||||||
key: PageStorageKey(logKey),
|
key: PageStorageKey(logKey),
|
||||||
@ -715,11 +703,10 @@ class _MarineManualReportStatusLogState
|
|||||||
_buildDetailRow('High-Level Status:', log.status),
|
_buildDetailRow('High-Level Status:', log.status),
|
||||||
_buildDetailRow('Server:', log.serverName),
|
_buildDetailRow('Server:', log.serverName),
|
||||||
_buildDetailRow('Report ID:', log.reportId ?? 'N/A'),
|
_buildDetailRow('Report ID:', log.reportId ?? 'N/A'),
|
||||||
_buildGranularStatus('API', log.apiStatusRaw), // <-- MODIFIED
|
_buildGranularStatus('API', log.apiStatusRaw),
|
||||||
_buildGranularStatus('FTP', log.ftpStatusRaw), // <-- MODIFIED
|
_buildGranularStatus('FTP', log.ftpStatusRaw),
|
||||||
_buildDetailRow('Message:', log.message),
|
_buildDetailRow('Message:', log.message),
|
||||||
|
|
||||||
// --- START: ADDED BUTTONS ---
|
|
||||||
const Divider(height: 10),
|
const Divider(height: 10),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
@ -727,19 +714,24 @@ class _MarineManualReportStatusLogState
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: Icon(Icons.list_alt, color: Theme.of(context).colorScheme.primary),
|
icon: Icon(Icons.list_alt,
|
||||||
label: Text('View Data', style: TextStyle(color: Theme.of(context).colorScheme.primary)),
|
color: Theme.of(context).colorScheme.primary),
|
||||||
|
label: Text('View Data',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary)),
|
||||||
onPressed: () => _showDataDialog(context, log),
|
onPressed: () => _showDataDialog(context, log),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: Icon(Icons.photo_library_outlined, color: Theme.of(context).colorScheme.secondary),
|
icon: Icon(Icons.photo_library_outlined,
|
||||||
label: Text('View Images', style: TextStyle(color: Theme.of(context).colorScheme.secondary)),
|
color: Theme.of(context).colorScheme.secondary),
|
||||||
|
label: Text('View Images',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.secondary)),
|
||||||
onPressed: () => _showImageDialog(context, log),
|
onPressed: () => _showImageDialog(context, log),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// --- END: ADDED BUTTONS ---
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -755,8 +747,8 @@ class _MarineManualReportStatusLogState
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child:
|
child: Text(label,
|
||||||
Text(label, style: const TextStyle(fontWeight: FontWeight.bold))),
|
style: const TextStyle(fontWeight: FontWeight.bold))),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(flex: 3, child: Text(value)),
|
Expanded(flex: 3, child: Text(value)),
|
||||||
],
|
],
|
||||||
@ -775,9 +767,9 @@ class _MarineManualReportStatusLogState
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: ADDED WIDGET-BASED HEADER HELPER ---
|
|
||||||
/// Builds a formatted category header row for the data list.
|
/// Builds a formatted category header row for the data list.
|
||||||
Widget _buildCategoryHeader(BuildContext context, String title, IconData icon) {
|
Widget _buildCategoryHeader(
|
||||||
|
BuildContext context, String title, IconData icon) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -796,50 +788,55 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: ADDED WIDGET-BASED HEADER HELPER ---
|
|
||||||
|
|
||||||
// --- START: RE-INTRODUCED TABLE-BASED HELPERS ---
|
/// Builds a formatted Section header (replacement for _buildCategoryRow).
|
||||||
/// Builds a formatted category header row for the data table.
|
Widget _buildSectionHeader(
|
||||||
TableRow _buildCategoryRow(BuildContext context, String title, IconData icon) {
|
BuildContext context, String title, IconData icon) {
|
||||||
return TableRow(
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.shade100,
|
color: Colors.grey.shade200,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0, left: 8.0, right: 8.0),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 20, color: Theme.of(context).primaryColor),
|
Icon(icon, size: 18, color: Theme.of(context).primaryColor),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
fontSize: 15,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox.shrink(), // Empty cell for the second column
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a formatted row for the data dialog.
|
/// Builds a responsive data row (replacement for _buildDataTableRow).
|
||||||
TableRow _buildDataTableRow(String label, String? value, {Color? valueColor}) {
|
/// Uses Column for better responsiveness on small screens if needed,
|
||||||
String displayValue = (value == null || value.isEmpty || value == 'null') ? 'N/A' : value;
|
/// or Row with Expanded to allow wrapping.
|
||||||
|
Widget _buildResponsiveDataRow(String label, String? value,
|
||||||
|
{Color? valueColor}) {
|
||||||
|
String displayValue =
|
||||||
|
(value == null || value.isEmpty || value == 'null') ? 'N/A' : value;
|
||||||
|
|
||||||
if (displayValue == '-999.0' || displayValue == '-999') {
|
if (displayValue == '-999.0' || displayValue == '-999') {
|
||||||
displayValue = 'N/A';
|
displayValue = 'N/A';
|
||||||
}
|
}
|
||||||
|
|
||||||
return TableRow(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 4.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Expanded(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@ -848,40 +845,22 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
const SizedBox(width: 8),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
child: Text(
|
child: Text(
|
||||||
displayValue,
|
displayValue,
|
||||||
style: TextStyle(fontSize: 14.0, color: valueColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a remark row for the data dialog.
|
|
||||||
TableRow _buildRemarkTableRow(String? remark) {
|
|
||||||
if (remark == null || remark.isEmpty) {
|
|
||||||
return const TableRow(children: [SizedBox.shrink(), SizedBox.shrink()]);
|
|
||||||
}
|
|
||||||
return TableRow(
|
|
||||||
children: [
|
|
||||||
const SizedBox.shrink(), // Empty cell for the label
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8.0, left: 8.0, right: 8.0),
|
|
||||||
child: Text(
|
|
||||||
"Remark: $remark",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13.0,
|
fontSize: 14.0,
|
||||||
color: Colors.grey.shade700,
|
color: (displayValue == 'N/A') ? Colors.grey : valueColor,
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: RE-INTRODUCED TABLE-BASED HELPERS ---
|
|
||||||
|
|
||||||
/// Shows the categorized and formatted data log in a dialog
|
/// Shows the categorized and formatted data log in a dialog
|
||||||
void _showDataDialog(BuildContext context, SubmissionLogEntry log) {
|
void _showDataDialog(BuildContext context, SubmissionLogEntry log) {
|
||||||
@ -889,26 +868,21 @@ class _MarineManualReportStatusLogState
|
|||||||
Widget dialogContent; // This will hold either a ListView or a Column
|
Widget dialogContent; // This will hold either a ListView or a Column
|
||||||
|
|
||||||
if (log.type == 'Pre-Departure Checklist') {
|
if (log.type == 'Pre-Departure Checklist') {
|
||||||
// --- START: Handle Pre-Departure Checklist (uses Column/ListView) ---
|
// --- Handle Pre-Departure Checklist (Existing Logic) ---
|
||||||
final items = Map<String, bool>.from(data['checklistItems'] ?? {});
|
final items = Map<String, bool>.from(data['checklistItems'] ?? {});
|
||||||
final remarks = Map<String, String>.from(data['remarks'] ?? {});
|
final remarks = Map<String, String>.from(data['remarks'] ?? {});
|
||||||
|
|
||||||
// 1. Build the list of widgets
|
|
||||||
final List<Widget> contentWidgets = [];
|
final List<Widget> contentWidgets = [];
|
||||||
|
|
||||||
// 2. Iterate over the DEFINED categories from the map
|
|
||||||
for (final categoryEntry in _checklistSections.entries) {
|
for (final categoryEntry in _checklistSections.entries) {
|
||||||
final categoryTitle = categoryEntry.key;
|
final categoryTitle = categoryEntry.key;
|
||||||
final categoryItems = categoryEntry.value;
|
final categoryItems = categoryEntry.value;
|
||||||
|
|
||||||
// Add the category header
|
contentWidgets.add(_buildCategoryHeader(
|
||||||
contentWidgets.add(_buildCategoryHeader(context, categoryTitle, Icons.check_box_outlined));
|
context, categoryTitle, Icons.check_box_outlined));
|
||||||
|
|
||||||
// 3. Add the items for that category
|
contentWidgets.addAll(categoryItems.map((itemName) {
|
||||||
contentWidgets.addAll(
|
final bool value = items[itemName] ?? false;
|
||||||
categoryItems.map((itemName) {
|
|
||||||
// Find the item's status and remark from the log data
|
|
||||||
final bool value = items[itemName] ?? false; // Default to 'No'
|
|
||||||
final String remark = remarks[itemName] ?? '';
|
final String remark = remarks[itemName] ?? '';
|
||||||
final String status = value ? 'Yes' : 'No';
|
final String status = value ? 'Yes' : 'No';
|
||||||
|
|
||||||
@ -917,15 +891,13 @@ class _MarineManualReportStatusLogState
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Row 1: Item and Status
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Item Name
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: Text(
|
child: Text(
|
||||||
itemName, // Use the name from the category list
|
itemName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
@ -933,14 +905,15 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
// Status
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Text(
|
child: Text(
|
||||||
status,
|
status,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
color: value ? Colors.green.shade700 : Colors.red.shade700,
|
color: value
|
||||||
|
? Colors.green.shade700
|
||||||
|
: Colors.red.shade700,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
@ -948,7 +921,6 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Row 2: Remark (only if it exists)
|
|
||||||
if (remark.isNotEmpty)
|
if (remark.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 6.0, left: 8.0),
|
padding: const EdgeInsets.only(top: 6.0, left: 8.0),
|
||||||
@ -979,27 +951,23 @@ class _MarineManualReportStatusLogState
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList()
|
}).toList());
|
||||||
);
|
|
||||||
|
|
||||||
// Add a divider after the category
|
|
||||||
contentWidgets.add(const Divider(height: 16));
|
contentWidgets.add(const Divider(height: 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Handle any items that were in the log but NOT in the category map
|
// Handle other items
|
||||||
final Set<String> allCategorizedItems = _checklistSections.values.expand((list) => list).toSet();
|
final Set<String> allCategorizedItems =
|
||||||
|
_checklistSections.values.expand((list) => list).toSet();
|
||||||
final List<Widget> otherItems = [];
|
final List<Widget> otherItems = [];
|
||||||
|
|
||||||
for (final itemEntry in items.entries) {
|
for (final itemEntry in items.entries) {
|
||||||
if (!allCategorizedItems.contains(itemEntry.key)) {
|
if (!allCategorizedItems.contains(itemEntry.key)) {
|
||||||
// This item was not in our hard-coded map
|
|
||||||
final key = itemEntry.key;
|
final key = itemEntry.key;
|
||||||
final value = itemEntry.value;
|
final value = itemEntry.value;
|
||||||
final status = value ? 'Yes' : 'No';
|
final status = value ? 'Yes' : 'No';
|
||||||
final remark = remarks[key] ?? '';
|
final remark = remarks[key] ?? '';
|
||||||
|
|
||||||
otherItems.add(
|
otherItems.add(Padding(
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -1007,9 +975,22 @@ class _MarineManualReportStatusLogState
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(flex: 3, child: Text(key, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0))),
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(key,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, fontSize: 14.0))),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(flex: 1, child: Text(status, style: TextStyle(fontSize: 14.0, color: value ? Colors.green.shade700 : Colors.red.shade700, fontWeight: FontWeight.bold), textAlign: TextAlign.end)),
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(status,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: value
|
||||||
|
? Colors.green.shade700
|
||||||
|
: Colors.red.shade700,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.end)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (remark.isNotEmpty)
|
if (remark.isNotEmpty)
|
||||||
@ -1018,49 +999,56 @@ class _MarineManualReportStatusLogState
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Remark: ", style: TextStyle(fontSize: 13.0, color: Colors.grey.shade700, fontStyle: FontStyle.italic)),
|
Text("Remark: ",
|
||||||
Expanded(child: Text(remark, style: TextStyle(fontSize: 13.0, color: Colors.grey.shade700, fontStyle: FontStyle.italic))),
|
style: TextStyle(
|
||||||
|
fontSize: 13.0,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
fontStyle: FontStyle.italic)),
|
||||||
|
Expanded(
|
||||||
|
child: Text(remark,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.0,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
fontStyle: FontStyle.italic))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherItems.isNotEmpty) {
|
if (otherItems.isNotEmpty) {
|
||||||
contentWidgets.add(_buildCategoryHeader(context, "Other Items", Icons.help_outline));
|
contentWidgets.add(
|
||||||
|
_buildCategoryHeader(context, "Other Items", Icons.help_outline));
|
||||||
contentWidgets.addAll(otherItems);
|
contentWidgets.addAll(otherItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentWidgets.isEmpty) {
|
if (contentWidgets.isEmpty) {
|
||||||
dialogContent = const Center(child: Text('No checklist items found.'));
|
dialogContent = const Center(child: Text('No checklist items found.'));
|
||||||
} else {
|
} else {
|
||||||
// Build the final Column
|
|
||||||
dialogContent = Column(
|
dialogContent = Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: contentWidgets,
|
children: contentWidgets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: Handle Pre-Departure Checklist ---
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// --- START: Handle ALL OTHER Log Types (uses Table) ---
|
// --- START: Handle ALL OTHER Log Types (NOW USING RESPONSIVE WIDGETS) ---
|
||||||
final List<TableRow> tableRows = [];
|
final List<Widget> listWidgets = [];
|
||||||
|
|
||||||
// --- Helper for nested maps ---
|
// --- Helper for nested maps ---
|
||||||
void addNestedMapRows(Map<String, dynamic> map) {
|
void addNestedMapRows(Map<String, dynamic> map) {
|
||||||
map.forEach((key, value) {
|
map.forEach((key, value) {
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
// Handle nested maps (e.g., ysiSensorChecks)
|
listWidgets.add(_buildResponsiveDataRow(key, ''));
|
||||||
tableRows.add(_buildDataTableRow(key, ''));
|
|
||||||
value.forEach((subKey, subValue) {
|
value.forEach((subKey, subValue) {
|
||||||
tableRows.add(_buildDataTableRow(' $subKey', subValue?.toString() ?? 'N/A'));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
' $subKey', subValue?.toString() ?? 'N/A'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
tableRows.add(_buildDataTableRow(key, value?.toString() ?? 'N/A'));
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow(key, value?.toString() ?? 'N/A'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1068,133 +1056,190 @@ class _MarineManualReportStatusLogState
|
|||||||
|
|
||||||
switch (log.type) {
|
switch (log.type) {
|
||||||
case 'Sonde Calibration':
|
case 'Sonde Calibration':
|
||||||
tableRows.add(_buildCategoryRow(context, 'Sonde Info', Icons.info_outline));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Sonde Serial #', _getString(data, 'sondeSerialNumber')));
|
_buildSectionHeader(context, 'Sonde Info', Icons.info_outline));
|
||||||
tableRows.add(_buildDataTableRow('Firmware Version', _getString(data, 'firmwareVersion')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('KOR Version', _getString(data, 'korVersion')));
|
'Sonde Serial #', _getString(data, 'sondeSerialNumber')));
|
||||||
tableRows.add(_buildDataTableRow('Location', _getString(data, 'location')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('Start Time', _getString(data, 'startDateTime')));
|
'Firmware Version', _getString(data, 'firmwareVersion')));
|
||||||
tableRows.add(_buildDataTableRow('End Time', _getString(data, 'endDateTime')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('Status', _getString(data, 'calibration_status')));
|
'KOR Version', _getString(data, 'korVersion')));
|
||||||
tableRows.add(_buildDataTableRow('Remarks', _getString(data, 'remarks')));
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('Location', _getString(data, 'location')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Start Time', _getString(data, 'startDateTime')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'End Time', _getString(data, 'endDateTime')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Status', _getString(data, 'calibration_status')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('Remarks', _getString(data, 'remarks')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'pH 7.0', Icons.science_outlined));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('MV', _getString(data, 'ph_7_mv')));
|
_buildSectionHeader(context, 'pH 7.0', Icons.science_outlined));
|
||||||
tableRows.add(_buildDataTableRow('Before', _getString(data, 'ph_7_before')));
|
listWidgets
|
||||||
tableRows.add(_buildDataTableRow('After', _getString(data, 'ph_7_after')));
|
.add(_buildResponsiveDataRow('MV', _getString(data, 'ph_7_mv')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('Before', _getString(data, 'ph_7_before')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('After', _getString(data, 'ph_7_after')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'pH 10.0', Icons.science_outlined));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('MV', _getString(data, 'ph_10_mv')));
|
_buildSectionHeader(context, 'pH 10.0', Icons.science_outlined));
|
||||||
tableRows.add(_buildDataTableRow('Before', _getString(data, 'ph_10_before')));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('After', _getString(data, 'ph_10_after')));
|
_buildResponsiveDataRow('MV', _getString(data, 'ph_10_mv')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Before', _getString(data, 'ph_10_before')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('After', _getString(data, 'ph_10_after')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Conductivity', Icons.thermostat));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Before', _getString(data, 'cond_before')));
|
_buildSectionHeader(context, 'Conductivity', Icons.thermostat));
|
||||||
tableRows.add(_buildDataTableRow('After', _getString(data, 'cond_after')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Before', _getString(data, 'cond_before')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('After', _getString(data, 'cond_after')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Dissolved Oxygen', Icons.air));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Before', _getString(data, 'do_before')));
|
_buildSectionHeader(context, 'Dissolved Oxygen', Icons.air));
|
||||||
tableRows.add(_buildDataTableRow('After', _getString(data, 'do_after')));
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('Before', _getString(data, 'do_before')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('After', _getString(data, 'do_after')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Turbidity', Icons.waves));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('0 NTU Before', _getString(data, 'turbidity_0_before')));
|
_buildSectionHeader(context, 'Turbidity', Icons.waves));
|
||||||
tableRows.add(_buildDataTableRow('0 NTU After', _getString(data, 'turbidity_0_after')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('124 NTU Before', _getString(data, 'turbidity_124_before')));
|
'0 NTU Before', _getString(data, 'turbidity_0_before')));
|
||||||
tableRows.add(_buildDataTableRow('124 NTU After', _getString(data, 'turbidity_124_after')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'0 NTU After', _getString(data, 'turbidity_0_after')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'124 NTU Before', _getString(data, 'turbidity_124_before')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'124 NTU After', _getString(data, 'turbidity_124_after')));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Equipment Maintenance':
|
case 'Equipment Maintenance':
|
||||||
tableRows.add(_buildCategoryRow(context, 'YSI Sonde Checks', Icons.build_circle_outlined));
|
listWidgets.add(_buildSectionHeader(
|
||||||
|
context, 'YSI Sonde Checks', Icons.build_circle_outlined));
|
||||||
if (data['ysiSondeChecks'] != null) {
|
if (data['ysiSondeChecks'] != null) {
|
||||||
addNestedMapRows(Map<String, dynamic>.from(data['ysiSondeChecks']));
|
addNestedMapRows(Map<String, dynamic>.from(data['ysiSondeChecks']));
|
||||||
}
|
}
|
||||||
tableRows.add(_buildDataTableRow('Comments', _getString(data, 'ysiSondeComments')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Comments', _getString(data, 'ysiSondeComments')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'YSI Sensor Checks', Icons.sensors));
|
listWidgets.add(_buildSectionHeader(
|
||||||
|
context, 'YSI Sensor Checks', Icons.sensors));
|
||||||
if (data['ysiSensorChecks'] != null) {
|
if (data['ysiSensorChecks'] != null) {
|
||||||
addNestedMapRows(Map<String, dynamic>.from(data['ysiSensorChecks']));
|
addNestedMapRows(
|
||||||
|
Map<String, dynamic>.from(data['ysiSensorChecks']));
|
||||||
}
|
}
|
||||||
tableRows.add(_buildDataTableRow('Comments', _getString(data, 'ysiSensorComments')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Comments', _getString(data, 'ysiSensorComments')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'YSI Replacements', Icons.published_with_changes));
|
listWidgets.add(_buildSectionHeader(
|
||||||
|
context, 'YSI Replacements', Icons.published_with_changes));
|
||||||
if (data['ysiReplacements'] != null) {
|
if (data['ysiReplacements'] != null) {
|
||||||
addNestedMapRows(Map<String, dynamic>.from(data['ysiReplacements']));
|
addNestedMapRows(
|
||||||
|
Map<String, dynamic>.from(data['ysiReplacements']));
|
||||||
}
|
}
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Van Dorn Checks', Icons.opacity));
|
listWidgets.add(
|
||||||
|
_buildSectionHeader(context, 'Van Dorn Checks', Icons.opacity));
|
||||||
if (data['vanDornChecks'] != null) {
|
if (data['vanDornChecks'] != null) {
|
||||||
addNestedMapRows(Map<String, dynamic>.from(data['vanDornChecks']));
|
addNestedMapRows(Map<String, dynamic>.from(data['vanDornChecks']));
|
||||||
}
|
}
|
||||||
tableRows.add(_buildDataTableRow('Comments', _getString(data, 'vanDornComments')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('Current Serial', _getString(data, 'vanDornCurrentSerial')));
|
'Comments', _getString(data, 'vanDornComments')));
|
||||||
tableRows.add(_buildDataTableRow('New Serial', _getString(data, 'vanDornNewSerial')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Current Serial', _getString(data, 'vanDornCurrentSerial')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'New Serial', _getString(data, 'vanDornNewSerial')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Van Dorn Replacements', Icons.published_with_changes));
|
listWidgets.add(_buildSectionHeader(context, 'Van Dorn Replacements',
|
||||||
|
Icons.published_with_changes));
|
||||||
if (data['vanDornReplacements'] != null) {
|
if (data['vanDornReplacements'] != null) {
|
||||||
addNestedMapRows(Map<String, dynamic>.from(data['vanDornReplacements']));
|
addNestedMapRows(
|
||||||
|
Map<String, dynamic>.from(data['vanDornReplacements']));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'NPE Report':
|
case 'NPE Report':
|
||||||
tableRows.add(_buildCategoryRow(context, 'Event Info', Icons.calendar_today));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Date', _getString(data, 'eventDate')));
|
_buildSectionHeader(context, 'Event Info', Icons.calendar_today));
|
||||||
tableRows.add(_buildDataTableRow('Time', _getString(data, 'eventTime')));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Sampler', _getString(data, 'firstSamplerName')));
|
_buildResponsiveDataRow('Date', _getString(data, 'eventDate')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('Time', _getString(data, 'eventTime')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Sampler', _getString(data, 'firstSamplerName')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Location', Icons.location_on_outlined));
|
listWidgets.add(_buildSectionHeader(
|
||||||
|
context, 'Location', Icons.location_on_outlined));
|
||||||
if (data['selectedStation'] != null) {
|
if (data['selectedStation'] != null) {
|
||||||
tableRows.add(_buildDataTableRow('Station', _getString(data['selectedStation'], 'man_station_name') ?? _getString(data['selectedStation'], 'tbl_station_name')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Station',
|
||||||
|
_getString(data['selectedStation'], 'man_station_name') ??
|
||||||
|
_getString(data['selectedStation'], 'tbl_station_name')));
|
||||||
} else {
|
} else {
|
||||||
tableRows.add(_buildDataTableRow('Location', _getString(data, 'locationDescription')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('State', _getString(data, 'stateName')));
|
'Location', _getString(data, 'locationDescription')));
|
||||||
|
listWidgets.add(
|
||||||
|
_buildResponsiveDataRow('State', _getString(data, 'stateName')));
|
||||||
}
|
}
|
||||||
tableRows.add(_buildDataTableRow('Latitude', _getString(data, 'latitude')));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Longitude', _getString(data, 'longitude')));
|
_buildResponsiveDataRow('Latitude', _getString(data, 'latitude')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Longitude', _getString(data, 'longitude')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Parameters', Icons.bar_chart));
|
listWidgets.add(
|
||||||
tableRows.add(_buildDataTableRow('Oxygen Conc (mg/L)', _getString(data, 'oxygenConcentration')));
|
_buildSectionHeader(context, 'Parameters', Icons.bar_chart));
|
||||||
tableRows.add(_buildDataTableRow('Oxygen Sat (%)', _getString(data, 'oxygenSaturation')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('pH', _getString(data, 'ph')));
|
'Oxygen Conc (mg/L)', _getString(data, 'oxygenConcentration')));
|
||||||
tableRows.add(_buildDataTableRow('Conductivity (µS/cm)', _getString(data, 'electricalConductivity')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('Temperature (°C)', _getString(data, 'temperature')));
|
'Oxygen Sat (%)', _getString(data, 'oxygenSaturation')));
|
||||||
tableRows.add(_buildDataTableRow('Turbidity (NTU)', _getString(data, 'turbidity')));
|
listWidgets.add(_buildResponsiveDataRow('pH', _getString(data, 'ph')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow('Conductivity (µS/cm)',
|
||||||
|
_getString(data, 'electricalConductivity')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Temperature (°C)', _getString(data, 'temperature')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Turbidity (NTU)', _getString(data, 'turbidity')));
|
||||||
|
|
||||||
tableRows.add(_buildCategoryRow(context, 'Observations', Icons.warning_amber_rounded));
|
listWidgets.add(_buildSectionHeader(
|
||||||
|
context, 'Observations', Icons.warning_amber_rounded));
|
||||||
if (data['fieldObservations'] != null) {
|
if (data['fieldObservations'] != null) {
|
||||||
final observations = Map<String, bool>.from(data['fieldObservations']);
|
final observations =
|
||||||
|
Map<String, bool>.from(data['fieldObservations']);
|
||||||
observations.forEach((key, value) {
|
observations.forEach((key, value) {
|
||||||
if(value) tableRows.add(_buildDataTableRow(key, 'Checked'));
|
if (value) listWidgets.add(_buildResponsiveDataRow(key, 'Checked'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tableRows.add(_buildDataTableRow('Other Remarks', _getString(data, 'othersObservationRemark')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
tableRows.add(_buildDataTableRow('Possible Source', _getString(data, 'possibleSource')));
|
'Other Remarks', _getString(data, 'othersObservationRemark')));
|
||||||
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Possible Source', _getString(data, 'possibleSource')));
|
||||||
if (data['selectedTarballClassification'] != null) {
|
if (data['selectedTarballClassification'] != null) {
|
||||||
tableRows.add(_buildDataTableRow('Tarball Class', _getString(data['selectedTarballClassification'], 'classification_name')));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Tarball Class',
|
||||||
|
_getString(data['selectedTarballClassification'],
|
||||||
|
'classification_name')));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
tableRows.add(_buildDataTableRow('Error', 'No data view configured for log type: ${log.type}'));
|
listWidgets.add(_buildResponsiveDataRow(
|
||||||
|
'Error', 'No data view configured for log type: ${log.type}'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the Table as the content for the 'else' block
|
// Assign the content
|
||||||
dialogContent = Table(
|
dialogContent = Column(
|
||||||
columnWidths: const {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
0: IntrinsicColumnWidth(), // Label column
|
children: listWidgets,
|
||||||
1: FlexColumnWidth(), // Value column
|
|
||||||
},
|
|
||||||
border: TableBorder(
|
|
||||||
horizontalInside: BorderSide(
|
|
||||||
color: Colors.grey.shade300,
|
|
||||||
width: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
children: tableRows,
|
|
||||||
);
|
);
|
||||||
// --- END: Handle ALL OTHER Log Types ---
|
// --- END: Handle ALL OTHER Log Types ---
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, dialogContent is guaranteed to be assigned
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@ -1203,7 +1248,7 @@ class _MarineManualReportStatusLogState
|
|||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: dialogContent, // <-- This is now safe
|
child: dialogContent,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@ -1218,7 +1263,6 @@ class _MarineManualReportStatusLogState
|
|||||||
}
|
}
|
||||||
// --- END: MODIFIED METHOD ---
|
// --- END: MODIFIED METHOD ---
|
||||||
|
|
||||||
|
|
||||||
/// Shows the image gallery dialog
|
/// Shows the image gallery dialog
|
||||||
void _showImageDialog(BuildContext context, SubmissionLogEntry log) {
|
void _showImageDialog(BuildContext context, SubmissionLogEntry log) {
|
||||||
final List<ImageLogEntry> imageEntries = [];
|
final List<ImageLogEntry> imageEntries = [];
|
||||||
@ -1261,7 +1305,8 @@ class _MarineManualReportStatusLogState
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final imageEntry = imageEntries[index];
|
final imageEntry = imageEntries[index];
|
||||||
final bool hasRemark = imageEntry.remark != null && imageEntry.remark!.isNotEmpty;
|
final bool hasRemark =
|
||||||
|
imageEntry.remark != null && imageEntry.remark!.isNotEmpty;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
@ -1329,7 +1374,8 @@ class _MarineManualReportStatusLogState
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for _showImageDialog
|
/// Helper for _showImageDialog
|
||||||
void _addImagesToList(SubmissionLogEntry log, Map<String, String?> imageRemarkMap, List<ImageLogEntry> imageEntries) {
|
void _addImagesToList(SubmissionLogEntry log,
|
||||||
|
Map<String, String?> imageRemarkMap, List<ImageLogEntry> imageEntries) {
|
||||||
for (final entry in imageRemarkMap.entries) {
|
for (final entry in imageRemarkMap.entries) {
|
||||||
final imageKey = entry.key;
|
final imageKey = entry.key;
|
||||||
final remarkKey = entry.value;
|
final remarkKey = entry.value;
|
||||||
@ -1338,7 +1384,8 @@ class _MarineManualReportStatusLogState
|
|||||||
if (path != null && path is String && path.isNotEmpty) {
|
if (path != null && path is String && path.isNotEmpty) {
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
final remark = (remarkKey != null ? log.rawData[remarkKey] as String? : null);
|
final remark =
|
||||||
|
(remarkKey != null ? log.rawData[remarkKey] as String? : null);
|
||||||
imageEntries.add(ImageLogEntry(file: file, remark: remark));
|
imageEntries.add(ImageLogEntry(file: file, remark: remark));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1374,17 +1421,21 @@ class _MarineManualReportStatusLogState
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('$type Status:', style: const TextStyle(fontWeight: FontWeight.bold)),
|
Text('$type Status:',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
...statuses.map((s) {
|
...statuses.map((s) {
|
||||||
final serverName = s['server_name'] ?? s['config_name'] ?? 'Server N/A';
|
final serverName =
|
||||||
|
s['server_name'] ?? s['config_name'] ?? 'Server N/A';
|
||||||
final status = s['message'] ?? 'N/A';
|
final status = s['message'] ?? 'N/A';
|
||||||
final bool isSuccess = s['success'] as bool? ?? false;
|
final bool isSuccess = s['success'] as bool? ?? false;
|
||||||
final IconData icon = isSuccess ? Icons.check_circle_outline : Icons.error_outline;
|
final IconData icon =
|
||||||
|
isSuccess ? Icons.check_circle_outline : Icons.error_outline;
|
||||||
final Color color = isSuccess ? Colors.green : Colors.red;
|
final Color color = isSuccess ? Colors.green : Colors.red;
|
||||||
String detailLabel = (s['type'] != null) ? '(${s['type']})' : '';
|
String detailLabel = (s['type'] != null) ? '(${s['type']})' : '';
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 8.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 3.0, horizontal: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 16, color: color),
|
Icon(icon, size: 16, color: color),
|
||||||
|
|||||||
@ -166,6 +166,16 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
_locationController.clear();
|
_locationController.clear();
|
||||||
_latController.clear();
|
_latController.clear();
|
||||||
_longController.clear();
|
_longController.clear();
|
||||||
|
|
||||||
|
// --- CHANGE: Clear measurement controllers so they are empty before reading ---
|
||||||
|
_doPercentController.clear();
|
||||||
|
_doMgLController.clear();
|
||||||
|
_phController.clear();
|
||||||
|
_condController.clear();
|
||||||
|
_turbController.clear();
|
||||||
|
_tempController.clear();
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_setDefaultDateTime(); // Reset to 'now'
|
_setDefaultDateTime(); // Reset to 'now'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -252,7 +262,7 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
final auth = Provider.of<AuthProvider>(context, listen: false);
|
final auth = Provider.of<AuthProvider>(context, listen: false);
|
||||||
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
||||||
|
|
||||||
_npeData.firstSamplerName = auth.profileData?['user_name'];
|
_npeData.firstSamplerName = auth.profileData?['first_name'];
|
||||||
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
||||||
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
||||||
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
||||||
@ -1007,6 +1017,10 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
isDense: true, // Helps with alignment
|
isDense: true, // Helps with alignment
|
||||||
|
// --- CHANGE: Added hint text to display when controller is empty (before start reading) ---
|
||||||
|
hintText: '-.--',
|
||||||
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
|
// -----------------------------------------------------------------------------------------
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -182,7 +182,7 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> {
|
|||||||
final auth = Provider.of<AuthProvider>(context, listen: false);
|
final auth = Provider.of<AuthProvider>(context, listen: false);
|
||||||
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
||||||
|
|
||||||
_npeData.firstSamplerName = auth.profileData?['user_name'];
|
_npeData.firstSamplerName = auth.profileData?['first_name'];
|
||||||
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
||||||
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
||||||
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
||||||
|
|||||||
@ -172,7 +172,7 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> {
|
|||||||
final auth = Provider.of<AuthProvider>(context, listen: false);
|
final auth = Provider.of<AuthProvider>(context, listen: false);
|
||||||
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
final service = Provider.of<MarineNpeReportService>(context, listen: false);
|
||||||
|
|
||||||
_npeData.firstSamplerName = auth.profileData?['user_name'];
|
_npeData.firstSamplerName = auth.profileData?['first_name'];
|
||||||
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
_npeData.firstSamplerUserId = auth.profileData?['user_id'];
|
||||||
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
_npeData.eventDate = _eventDateTimeController.text.split(' ')[0];
|
||||||
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
_npeData.eventTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : '';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// lib/screens/marine/manual/widgets/in_situ_step_4_summary.dart
|
// lib/screens/marine/manual/widgets/in_situ_step_4_summary.dart
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data'; // <-- Required for Uint8List
|
import 'dart:typed_data'; // Required for Uint8List
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@ -12,8 +12,7 @@ import '../reports/npe_report_from_in_situ.dart';
|
|||||||
|
|
||||||
class InSituStep4Summary extends StatefulWidget {
|
class InSituStep4Summary extends StatefulWidget {
|
||||||
final InSituSamplingData data;
|
final InSituSamplingData data;
|
||||||
final Future<Map<String, dynamic>> Function()
|
final Future<Map<String, dynamic>> Function() onSubmit; // Expects a function that returns the submission result
|
||||||
onSubmit; // Expects a function that returns the submission result
|
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
const InSituStep4Summary({
|
const InSituStep4Summary({
|
||||||
@ -43,11 +42,20 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
'batteryVoltage': 'Battery',
|
'batteryVoltage': 'Battery',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- START: FIXED OUT OF BOUNDS LOGIC ---
|
||||||
Set<String> _getOutOfBoundsKeys(BuildContext context) {
|
Set<String> _getOutOfBoundsKeys(BuildContext context) {
|
||||||
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||||
final marineLimits = authProvider.marineParameterLimits ?? [];
|
final marineLimits = authProvider.marineParameterLimits ?? [];
|
||||||
final Set<String> invalidKeys = {};
|
final Set<String> invalidKeys = {};
|
||||||
final int? stationId = widget.data.selectedStation?['man_station_id'];
|
|
||||||
|
// ✅ FIX: Robustly try to get station ID using both potential keys
|
||||||
|
final dynamic stationId = widget.data.selectedStation?['station_id'] ??
|
||||||
|
widget.data.selectedStation?['man_station_id'];
|
||||||
|
|
||||||
|
if (stationId == null) {
|
||||||
|
// Without a station ID, we cannot check station-specific limits
|
||||||
|
return invalidKeys;
|
||||||
|
}
|
||||||
|
|
||||||
final readings = {
|
final readings = {
|
||||||
'oxygenConcentration': widget.data.oxygenConcentration,
|
'oxygenConcentration': widget.data.oxygenConcentration,
|
||||||
@ -75,20 +83,13 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
final limitName = _parameterKeyToLimitName[key];
|
final limitName = _parameterKeyToLimitName[key];
|
||||||
if (limitName == null) return;
|
if (limitName == null) return;
|
||||||
|
|
||||||
Map<String, dynamic> limitData = {};
|
// Find limits for this specific station
|
||||||
|
final limitData = marineLimits.firstWhere(
|
||||||
if (stationId != null) {
|
|
||||||
// --- START FIX: Use type-safe comparison for station_id ---
|
|
||||||
// This ensures that the comparison works regardless of whether
|
|
||||||
// station_id is stored as a number (e.g., 123) or a string (e.g., "123").
|
|
||||||
limitData = marineLimits.firstWhere(
|
|
||||||
(l) =>
|
(l) =>
|
||||||
l['param_parameter_list'] == limitName &&
|
l['param_parameter_list'] == limitName &&
|
||||||
l['station_id']?.toString() == stationId.toString(),
|
l['station_id']?.toString() == stationId.toString(),
|
||||||
orElse: () => {},
|
orElse: () => {},
|
||||||
);
|
);
|
||||||
// --- END FIX ---
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limitData.isNotEmpty) {
|
if (limitData.isNotEmpty) {
|
||||||
final lowerLimit = parseLimitValue(limitData['param_lower_limit']);
|
final lowerLimit = parseLimitValue(limitData['param_lower_limit']);
|
||||||
@ -103,6 +104,7 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
|
|
||||||
return invalidKeys;
|
return invalidKeys;
|
||||||
}
|
}
|
||||||
|
// --- END: FIXED OUT OF BOUNDS LOGIC ---
|
||||||
|
|
||||||
/// Checks captured data against NPE limits and returns detailed information for the dialog.
|
/// Checks captured data against NPE limits and returns detailed information for the dialog.
|
||||||
List<Map<String, dynamic>> _getNpeTriggeredParameters(BuildContext context) {
|
List<Map<String, dynamic>> _getNpeTriggeredParameters(BuildContext context) {
|
||||||
@ -136,7 +138,6 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
final limitName = _parameterKeyToLimitName[key];
|
final limitName = _parameterKeyToLimitName[key];
|
||||||
if (limitName == null) return;
|
if (limitName == null) return;
|
||||||
|
|
||||||
// NPE limits are general and NOT station-specific, so this is correct.
|
|
||||||
final limitData = npeLimits.firstWhere(
|
final limitData = npeLimits.firstWhere(
|
||||||
(l) => l['param_parameter_list'] == limitName,
|
(l) => l['param_parameter_list'] == limitName,
|
||||||
orElse: () => {},
|
orElse: () => {},
|
||||||
@ -307,7 +308,6 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the complete submission flow: NPE check, submission, and UI feedback/navigation.
|
|
||||||
Future<void> _handleSubmit(BuildContext context) async {
|
Future<void> _handleSubmit(BuildContext context) async {
|
||||||
if (_isHandlingSubmit || widget.isLoading) return;
|
if (_isHandlingSubmit || widget.isLoading) return;
|
||||||
|
|
||||||
@ -327,14 +327,12 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
} else if (userChoice == false) {
|
} else if (userChoice == false) {
|
||||||
proceedWithSubmission = true;
|
proceedWithSubmission = true;
|
||||||
}
|
}
|
||||||
// If userChoice is null (dialog dismissed), we do nothing.
|
|
||||||
} else {
|
} else {
|
||||||
// No NPE hit, proceed normally.
|
|
||||||
proceedWithSubmission = true;
|
proceedWithSubmission = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proceedWithSubmission) {
|
if (proceedWithSubmission) {
|
||||||
final result = await widget.onSubmit(); // This calls the logic in the parent
|
final result = await widget.onSubmit();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final message = result['message'] ?? 'An unknown error occurred.';
|
final message = result['message'] ?? 'An unknown error occurred.';
|
||||||
@ -348,14 +346,13 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
|
|
||||||
if (result['success'] == true) {
|
if (result['success'] == true) {
|
||||||
if (shouldOpenNpeReport) {
|
if (shouldOpenNpeReport) {
|
||||||
// Navigate to the correct screen without passing data, as requested.
|
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const NPEReportFromInSitu()),
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const NPEReportFromInSitu()),
|
||||||
(route) => route.isFirst,
|
(route) => route.isFirst,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Submission successful, and no NPE report needed, so go home.
|
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,6 +366,7 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Get the invalid keys based on station limits
|
||||||
final outOfBoundsKeys = _getOutOfBoundsKeys(context);
|
final outOfBoundsKeys = _getOutOfBoundsKeys(context);
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
@ -463,6 +461,7 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
_buildDetailRow("Capture Time:",
|
_buildDetailRow("Capture Time:",
|
||||||
"${widget.data.dataCaptureDate} ${widget.data.dataCaptureTime}"),
|
"${widget.data.dataCaptureDate} ${widget.data.dataCaptureTime}"),
|
||||||
const Divider(height: 20),
|
const Divider(height: 20),
|
||||||
|
// Pass 'isOutOfBounds' based on the calculated keys
|
||||||
_buildParameterListItem(context,
|
_buildParameterListItem(context,
|
||||||
icon: Icons.air,
|
icon: Icons.air,
|
||||||
label: "Oxygen Conc.",
|
label: "Oxygen Conc.",
|
||||||
@ -635,7 +634,6 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (image != null)
|
if (image != null)
|
||||||
// --- START MODIFICATION: Use FutureBuilder to load bytes async ---
|
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
child: FutureBuilder<Uint8List>(
|
child: FutureBuilder<Uint8List>(
|
||||||
@ -667,7 +665,6 @@ class _InSituStep4SummaryState extends State<InSituStep4Summary> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// --- END MODIFICATION ---
|
|
||||||
else
|
else
|
||||||
Container(
|
Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
|
|||||||
@ -6,10 +6,7 @@ import 'package:environment_monitoring_app/auth_provider.dart';
|
|||||||
import 'package:environment_monitoring_app/services/settings_service.dart';
|
import 'package:environment_monitoring_app/services/settings_service.dart';
|
||||||
|
|
||||||
class TelegramAlertSettingsScreen extends StatelessWidget {
|
class TelegramAlertSettingsScreen extends StatelessWidget {
|
||||||
// --- START MODIFICATION ---
|
|
||||||
// Removed 'const' from the constructor to fix the error.
|
|
||||||
TelegramAlertSettingsScreen({super.key});
|
TelegramAlertSettingsScreen({super.key});
|
||||||
// --- END MODIFICATION ---
|
|
||||||
|
|
||||||
// Helper service for parsing settings
|
// Helper service for parsing settings
|
||||||
final SettingsService _settingsService = SettingsService();
|
final SettingsService _settingsService = SettingsService();
|
||||||
@ -56,6 +53,7 @@ class TelegramAlertSettingsScreen extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
// --- MARINE SECTION ---
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
title: const Text('Marine Alerts',
|
title: const Text('Marine Alerts',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
@ -69,8 +67,14 @@ class TelegramAlertSettingsScreen extends StatelessWidget {
|
|||||||
'Investigative',
|
'Investigative',
|
||||||
_settingsService
|
_settingsService
|
||||||
.getMarineInvestigativeChatId(appSettings)),
|
.getMarineInvestigativeChatId(appSettings)),
|
||||||
|
// --- ADDED: Marine Report ---
|
||||||
|
_buildChatIdEntry(
|
||||||
|
'NPE Report',
|
||||||
|
_settingsService.getMarineReportChatId(appSettings)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// --- RIVER SECTION ---
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
title: const Text('River Alerts',
|
title: const Text('River Alerts',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
@ -86,8 +90,14 @@ class TelegramAlertSettingsScreen extends StatelessWidget {
|
|||||||
'Investigative',
|
'Investigative',
|
||||||
_settingsService
|
_settingsService
|
||||||
.getRiverInvestigativeChatId(appSettings)),
|
.getRiverInvestigativeChatId(appSettings)),
|
||||||
|
// --- ADDED: River Report ---
|
||||||
|
_buildChatIdEntry(
|
||||||
|
'NPE Report',
|
||||||
|
_settingsService.getRiverReportChatId(appSettings)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// --- AIR SECTION ---
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
title: const Text('Air Alerts',
|
title: const Text('Air Alerts',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
|||||||
@ -663,7 +663,7 @@ class MarineInSituSamplingService {
|
|||||||
final submitter = data.firstSamplerName ?? 'N/A';
|
final submitter = data.firstSamplerName ?? 'N/A';
|
||||||
|
|
||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln('✅ *In-Situ Sample $submissionType Submitted:*')
|
..writeln('✅ *Marine In-Situ Sample $submissionType Submitted:*')
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln('*Station Name & Code:* $stationName ($stationCode)')
|
..writeln('*Station Name & Code:* $stationName ($stationCode)')
|
||||||
// --- START MODIFICATION ---
|
// --- START MODIFICATION ---
|
||||||
@ -736,8 +736,9 @@ class MarineInSituSamplingService {
|
|||||||
final allLimits = await _dbHelper.loadMarineParameterLimits() ?? [];
|
final allLimits = await _dbHelper.loadMarineParameterLimits() ?? [];
|
||||||
if (allLimits.isEmpty) return "";
|
if (allLimits.isEmpty) return "";
|
||||||
|
|
||||||
// --- START FIX: Use correct key 'man_station_id' ---
|
// --- START FIX: Use correct key 'station_id' with fallback to 'man_station_id' ---
|
||||||
final dynamic stationId = data.selectedStation?['man_station_id'];
|
// The original code was strictly checking 'man_station_id', but API form data uses 'station_id'.
|
||||||
|
final dynamic stationId = data.selectedStation?['station_id'] ?? data.selectedStation?['man_station_id'];
|
||||||
// --- END FIX ---
|
// --- END FIX ---
|
||||||
if (stationId == null) return ""; // Cannot check limits without a station ID
|
if (stationId == null) return ""; // Cannot check limits without a station ID
|
||||||
|
|
||||||
@ -880,7 +881,7 @@ class MarineInSituSamplingService {
|
|||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln(' ')
|
..writeln(' ')
|
||||||
..writeln('🚨 *NPE Parameter Limit Detected:*')
|
..writeln('🚨 *Marine NPE Parameter Limit Detected:*')
|
||||||
..writeln('The following parameters triggered an NPE alert:');
|
..writeln('The following parameters triggered an NPE alert:');
|
||||||
buffer.writeAll(npeMessages, '\n');
|
buffer.writeAll(npeMessages, '\n');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// lib/services/marine_npe_report_service.dart
|
// lib/services/marine_tarball_sampling_service.dart
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|||||||
@ -9,17 +9,28 @@ import 'package:environment_monitoring_app/models/in_situ_sampling_data.dart';
|
|||||||
import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/models/river_in_situ_sampling_data.dart';
|
import 'package:environment_monitoring_app/models/river_in_situ_sampling_data.dart';
|
||||||
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart';
|
||||||
// *** ADDED: Import River Investigative Model and Service ***
|
|
||||||
import 'package:environment_monitoring_app/models/river_inves_manual_sampling_data.dart';
|
import 'package:environment_monitoring_app/models/river_inves_manual_sampling_data.dart';
|
||||||
import 'package:environment_monitoring_app/services/river_investigative_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/river_investigative_sampling_service.dart';
|
||||||
// *** END ADDED ***
|
|
||||||
import 'package:environment_monitoring_app/models/marine_inves_manual_sampling_data.dart';
|
import 'package:environment_monitoring_app/models/marine_inves_manual_sampling_data.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_investigative_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/marine_investigative_sampling_service.dart';
|
||||||
import 'package:environment_monitoring_app/models/tarball_data.dart';
|
import 'package:environment_monitoring_app/models/tarball_data.dart';
|
||||||
import 'package:environment_monitoring_app/services/marine_tarball_sampling_service.dart';
|
import 'package:environment_monitoring_app/services/marine_tarball_sampling_service.dart';
|
||||||
//import 'package:environment_monitoring_app/services/api_service.dart';
|
|
||||||
import 'package:environment_monitoring_app/services/database_helper.dart';
|
|
||||||
|
|
||||||
|
// --- MARINE REPORT IMPORTS ---
|
||||||
|
import 'package:environment_monitoring_app/models/marine_manual_npe_report_data.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/marine_npe_report_service.dart';
|
||||||
|
|
||||||
|
import 'package:environment_monitoring_app/models/marine_manual_pre_departure_checklist_data.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/marine_manual_pre_departure_service.dart';
|
||||||
|
|
||||||
|
import 'package:environment_monitoring_app/models/marine_manual_sonde_calibration_data.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/marine_manual_sonde_calibration_service.dart';
|
||||||
|
|
||||||
|
import 'package:environment_monitoring_app/models/marine_manual_equipment_maintenance_data.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/marine_manual_equipment_maintenance_service.dart';
|
||||||
|
// --- END MARINE REPORT IMPORTS ---
|
||||||
|
|
||||||
|
import 'package:environment_monitoring_app/services/database_helper.dart';
|
||||||
import 'package:environment_monitoring_app/services/base_api_service.dart';
|
import 'package:environment_monitoring_app/services/base_api_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/ftp_service.dart';
|
import 'package:environment_monitoring_app/services/ftp_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
||||||
@ -33,33 +44,51 @@ class RetryService {
|
|||||||
final ServerConfigService _serverConfigService = ServerConfigService();
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
bool _isProcessing = false;
|
bool _isProcessing = false;
|
||||||
|
|
||||||
|
// Sampling Services
|
||||||
MarineInSituSamplingService? _marineInSituService;
|
MarineInSituSamplingService? _marineInSituService;
|
||||||
RiverInSituSamplingService? _riverInSituService;
|
RiverInSituSamplingService? _riverInSituService;
|
||||||
MarineInvestigativeSamplingService? _marineInvestigativeService;
|
MarineInvestigativeSamplingService? _marineInvestigativeService;
|
||||||
MarineTarballSamplingService? _marineTarballService;
|
|
||||||
// *** ADDED: River Investigative Service member ***
|
|
||||||
RiverInvestigativeSamplingService? _riverInvestigativeService;
|
RiverInvestigativeSamplingService? _riverInvestigativeService;
|
||||||
|
MarineTarballSamplingService? _marineTarballService;
|
||||||
|
|
||||||
|
// Report Services
|
||||||
|
MarineNpeReportService? _marineNpeService;
|
||||||
|
// *** ADDED: Other Marine Report Services ***
|
||||||
|
MarineManualPreDepartureService? _marinePreDepartureService;
|
||||||
|
MarineManualSondeCalibrationService? _marineSondeCalibrationService;
|
||||||
|
MarineManualEquipmentMaintenanceService? _marineEquipmentMaintenanceService;
|
||||||
// *** END ADDED ***
|
// *** END ADDED ***
|
||||||
|
|
||||||
AuthProvider? _authProvider;
|
AuthProvider? _authProvider;
|
||||||
|
|
||||||
// *** MODIFIED: Added riverInvestigativeService to initialize ***
|
|
||||||
void initialize({
|
void initialize({
|
||||||
required MarineInSituSamplingService marineInSituService,
|
required MarineInSituSamplingService marineInSituService,
|
||||||
required RiverInSituSamplingService riverInSituService,
|
required RiverInSituSamplingService riverInSituService,
|
||||||
required MarineInvestigativeSamplingService marineInvestigativeService,
|
required MarineInvestigativeSamplingService marineInvestigativeService,
|
||||||
required RiverInvestigativeSamplingService riverInvestigativeService, // <-- Added parameter
|
required RiverInvestigativeSamplingService riverInvestigativeService,
|
||||||
required MarineTarballSamplingService marineTarballService,
|
required MarineTarballSamplingService marineTarballService,
|
||||||
|
|
||||||
|
// Added Marine Report Services
|
||||||
|
required MarineNpeReportService marineNpeService,
|
||||||
|
required MarineManualPreDepartureService marinePreDepartureService,
|
||||||
|
required MarineManualSondeCalibrationService marineSondeCalibrationService,
|
||||||
|
required MarineManualEquipmentMaintenanceService marineEquipmentMaintenanceService,
|
||||||
|
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
}) {
|
}) {
|
||||||
_marineInSituService = marineInSituService;
|
_marineInSituService = marineInSituService;
|
||||||
_riverInSituService = riverInSituService;
|
_riverInSituService = riverInSituService;
|
||||||
_marineInvestigativeService = marineInvestigativeService;
|
_marineInvestigativeService = marineInvestigativeService;
|
||||||
_riverInvestigativeService = riverInvestigativeService; // <-- Assign parameter
|
_riverInvestigativeService = riverInvestigativeService;
|
||||||
_marineTarballService = marineTarballService;
|
_marineTarballService = marineTarballService;
|
||||||
|
|
||||||
|
_marineNpeService = marineNpeService;
|
||||||
|
_marinePreDepartureService = marinePreDepartureService;
|
||||||
|
_marineSondeCalibrationService = marineSondeCalibrationService;
|
||||||
|
_marineEquipmentMaintenanceService = marineEquipmentMaintenanceService;
|
||||||
|
|
||||||
_authProvider = authProvider;
|
_authProvider = authProvider;
|
||||||
}
|
}
|
||||||
// *** END MODIFIED ***
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds a generic, complex task to the queue, to be handled by a background processor.
|
/// Adds a generic, complex task to the queue, to be handled by a background processor.
|
||||||
Future<void> queueTask({
|
Future<void> queueTask({
|
||||||
@ -68,7 +97,7 @@ class RetryService {
|
|||||||
}) async {
|
}) async {
|
||||||
await _dbHelper.queueFailedRequest({
|
await _dbHelper.queueFailedRequest({
|
||||||
'type': type,
|
'type': type,
|
||||||
'endpoint_or_path': 'N/A', // Not applicable for complex tasks initially
|
'endpoint_or_path': 'N/A',
|
||||||
'payload': jsonEncode(payload),
|
'payload': jsonEncode(payload),
|
||||||
'timestamp': DateTime.now().toIso8601String(),
|
'timestamp': DateTime.now().toIso8601String(),
|
||||||
'status': 'pending',
|
'status': 'pending',
|
||||||
@ -84,13 +113,12 @@ class RetryService {
|
|||||||
Map<String, String>? fields,
|
Map<String, String>? fields,
|
||||||
Map<String, File>? files,
|
Map<String, File>? files,
|
||||||
}) async {
|
}) async {
|
||||||
// Convert File objects to paths for JSON serialization
|
|
||||||
final serializableFiles = files?.map((key, value) => MapEntry(key, value.path));
|
final serializableFiles = files?.map((key, value) => MapEntry(key, value.path));
|
||||||
final payload = {
|
final payload = {
|
||||||
'method': method,
|
'method': method,
|
||||||
'body': body,
|
'body': body,
|
||||||
'fields': fields,
|
'fields': fields,
|
||||||
'files': serializableFiles, // Store paths instead of File objects
|
'files': serializableFiles,
|
||||||
};
|
};
|
||||||
await _dbHelper.queueFailedRequest({
|
await _dbHelper.queueFailedRequest({
|
||||||
'type': 'api',
|
'type': 'api',
|
||||||
@ -106,11 +134,11 @@ class RetryService {
|
|||||||
Future<void> addFtpToQueue({
|
Future<void> addFtpToQueue({
|
||||||
required String localFilePath,
|
required String localFilePath,
|
||||||
required String remotePath,
|
required String remotePath,
|
||||||
required int ftpConfigId, // Added to specify which destination failed
|
required int ftpConfigId,
|
||||||
}) async {
|
}) async {
|
||||||
final payload = {
|
final payload = {
|
||||||
'localFilePath': localFilePath,
|
'localFilePath': localFilePath,
|
||||||
'ftpConfigId': ftpConfigId, // Store the specific config ID
|
'ftpConfigId': ftpConfigId,
|
||||||
};
|
};
|
||||||
await _dbHelper.queueFailedRequest({
|
await _dbHelper.queueFailedRequest({
|
||||||
'type': 'ftp',
|
'type': 'ftp',
|
||||||
@ -123,12 +151,10 @@ class RetryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Retrieves all tasks currently in the 'pending' state from the queue.
|
|
||||||
Future<List<Map<String, dynamic>>> getPendingTasks() {
|
Future<List<Map<String, dynamic>>> getPendingTasks() {
|
||||||
return _dbHelper.getPendingRequests();
|
return _dbHelper.getPendingRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the entire queue of pending tasks.
|
|
||||||
Future<void> processRetryQueue() async {
|
Future<void> processRetryQueue() async {
|
||||||
if (_isProcessing) {
|
if (_isProcessing) {
|
||||||
debugPrint("[RetryService] ⏳ Queue is already being processed. Skipping.");
|
debugPrint("[RetryService] ⏳ Queue is already being processed. Skipping.");
|
||||||
@ -144,7 +170,6 @@ class RetryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check internet connection *before* processing
|
|
||||||
if (_authProvider == null || !await _authProvider!.isConnected()) {
|
if (_authProvider == null || !await _authProvider!.isConnected()) {
|
||||||
debugPrint("[RetryService] ❌ No internet connection. Aborting queue processing.");
|
debugPrint("[RetryService] ❌ No internet connection. Aborting queue processing.");
|
||||||
_isProcessing = false;
|
_isProcessing = false;
|
||||||
@ -152,44 +177,36 @@ class RetryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debugPrint("[RetryService] 🔎 Found ${pendingTasks.length} pending tasks.");
|
debugPrint("[RetryService] 🔎 Found ${pendingTasks.length} pending tasks.");
|
||||||
// Process tasks one by one
|
|
||||||
for (final task in pendingTasks) {
|
for (final task in pendingTasks) {
|
||||||
// Add safety check in case a task is deleted mid-processing by another call
|
|
||||||
if (await _dbHelper.getRequestById(task['id'] as int) != null) {
|
if (await _dbHelper.getRequestById(task['id'] as int) != null) {
|
||||||
await retryTask(task['id'] as int);
|
await retryTask(task['id'] as int);
|
||||||
}
|
}
|
||||||
// Optional: Add a small delay between tasks if needed
|
|
||||||
// await Future.delayed(Duration(seconds: 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint("[RetryService] ⏹️ Finished processing retry queue.");
|
debugPrint("[RetryService] ⏹️ Finished processing retry queue.");
|
||||||
_isProcessing = false;
|
_isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to re-execute a single failed task from the queue.
|
|
||||||
/// Returns `true` on success, `false` on failure.
|
|
||||||
Future<bool> retryTask(int taskId) async {
|
Future<bool> retryTask(int taskId) async {
|
||||||
final task = await _dbHelper.getRequestById(taskId);
|
final task = await _dbHelper.getRequestById(taskId);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
debugPrint("Retry failed: Task with ID $taskId not found in the queue (might have been processed already).");
|
debugPrint("Retry failed: Task with ID $taskId not found in the queue.");
|
||||||
return false; // Task doesn't exist or was processed elsewhere
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Map<String, dynamic> payload; // Declare outside try-catch
|
Map<String, dynamic> payload;
|
||||||
final String taskType = task['type'] as String; // Get type early for logging
|
final String taskType = task['type'] as String;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = jsonDecode(task['payload'] as String); // Decode payload inside try
|
payload = jsonDecode(task['payload'] as String);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error decoding payload for task $taskId (Type: $taskType): $e. Removing invalid task.");
|
debugPrint("Error decoding payload for task $taskId (Type: $taskType): $e. Removing invalid task.");
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false; // Cannot process without valid payload
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure AuthProvider is initialized and we are online (checked in processRetryQueue)
|
|
||||||
if (_authProvider == null) {
|
if (_authProvider == null) {
|
||||||
debugPrint("RetryService has not been initialized. Cannot process task $taskId.");
|
debugPrint("RetryService has not been initialized. Cannot process task $taskId.");
|
||||||
return false;
|
return false;
|
||||||
@ -197,121 +214,66 @@ class RetryService {
|
|||||||
|
|
||||||
// --- Complex Task Handlers ---
|
// --- Complex Task Handlers ---
|
||||||
if (taskType == 'insitu_submission') {
|
if (taskType == 'insitu_submission') {
|
||||||
debugPrint("Retrying complex task 'insitu_submission' with ID $taskId.");
|
if (_marineInSituService == null) return false;
|
||||||
if (_marineInSituService == null) {
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
debugPrint("Retry failed: MarineInSituSamplingService not initialized.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String logDirectoryPath = payload['localLogPath']; // Path to the directory
|
|
||||||
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
final file = File(jsonFilePath);
|
final file = File(jsonFilePath);
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed: Source log file no longer exists at $jsonFilePath");
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final content = await file.readAsString();
|
final content = await file.readAsString();
|
||||||
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
|
||||||
final InSituSamplingData dataToResubmit = InSituSamplingData.fromJson(jsonData);
|
|
||||||
|
|
||||||
// Re-run the original submission logic, passing the log directory
|
|
||||||
final result = await _marineInSituService!.submitInSituSample(
|
final result = await _marineInSituService!.submitInSituSample(
|
||||||
data: dataToResubmit,
|
data: InSituSamplingData.fromJson(jsonDecode(content)),
|
||||||
appSettings: _authProvider!.appSettings, // Get current settings
|
appSettings: _authProvider!.appSettings,
|
||||||
authProvider: _authProvider!,
|
authProvider: _authProvider!,
|
||||||
logDirectory: logDirectoryPath, // Pass directory to update log
|
logDirectory: logDirectoryPath,
|
||||||
);
|
);
|
||||||
success = result['success'];
|
success = result['success'];
|
||||||
|
|
||||||
} else if (taskType == 'river_insitu_submission') {
|
} else if (taskType == 'river_insitu_submission') {
|
||||||
debugPrint("Retrying complex task 'river_insitu_submission' with ID $taskId.");
|
if (_riverInSituService == null) return false;
|
||||||
if (_riverInSituService == null) {
|
final String jsonFilePath = payload['localLogPath'];
|
||||||
debugPrint("Retry failed: RiverInSituSamplingService not initialized.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String jsonFilePath = payload['localLogPath']; // Path to the JSON file
|
|
||||||
final file = File(jsonFilePath);
|
final file = File(jsonFilePath);
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed: Source log file no longer exists at $jsonFilePath");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final String logDirectoryPath = p.dirname(jsonFilePath); // Get directory from file path
|
|
||||||
|
|
||||||
|
|
||||||
final content = await file.readAsString();
|
|
||||||
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
|
||||||
final RiverInSituSamplingData dataToResubmit = RiverInSituSamplingData.fromJson(jsonData);
|
|
||||||
|
|
||||||
final result = await _riverInSituService!.submitData(
|
final result = await _riverInSituService!.submitData(
|
||||||
data: dataToResubmit,
|
data: RiverInSituSamplingData.fromJson(jsonDecode(await file.readAsString())),
|
||||||
appSettings: _authProvider!.appSettings,
|
appSettings: _authProvider!.appSettings,
|
||||||
authProvider: _authProvider!,
|
authProvider: _authProvider!,
|
||||||
logDirectory: logDirectoryPath,
|
logDirectory: p.dirname(jsonFilePath),
|
||||||
);
|
);
|
||||||
success = result['success'];
|
success = result['success'];
|
||||||
|
|
||||||
// *** ADDED: Handler for river_investigative_submission ***
|
|
||||||
} else if (taskType == 'river_investigative_submission') {
|
} else if (taskType == 'river_investigative_submission') {
|
||||||
debugPrint("Retrying complex task 'river_investigative_submission' with ID $taskId.");
|
if (_riverInvestigativeService == null) return false;
|
||||||
if (_riverInvestigativeService == null) {
|
final String jsonFilePath = payload['localLogPath'];
|
||||||
debugPrint("Retry failed: RiverInvestigativeSamplingService not initialized.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String jsonFilePath = payload['localLogPath']; // Path to the JSON file
|
|
||||||
final file = File(jsonFilePath);
|
final file = File(jsonFilePath);
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed: Source log file no longer exists at $jsonFilePath");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final String logDirectoryPath = p.dirname(jsonFilePath); // Get directory from file path
|
|
||||||
|
|
||||||
final content = await file.readAsString();
|
|
||||||
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
|
||||||
// Use the correct Investigative data model
|
|
||||||
final RiverInvesManualSamplingData dataToResubmit = RiverInvesManualSamplingData.fromJson(jsonData);
|
|
||||||
|
|
||||||
// Call the submitData method from the Investigative service
|
|
||||||
final result = await _riverInvestigativeService!.submitData(
|
final result = await _riverInvestigativeService!.submitData(
|
||||||
data: dataToResubmit,
|
data: RiverInvesManualSamplingData.fromJson(jsonDecode(await file.readAsString())),
|
||||||
appSettings: _authProvider!.appSettings,
|
appSettings: _authProvider!.appSettings,
|
||||||
authProvider: _authProvider!,
|
authProvider: _authProvider!,
|
||||||
logDirectory: logDirectoryPath,
|
logDirectory: p.dirname(jsonFilePath),
|
||||||
);
|
);
|
||||||
success = result['success'];
|
success = result['success'];
|
||||||
// *** END ADDED ***
|
|
||||||
|
|
||||||
} else if (taskType == 'investigative_submission') {
|
} else if (taskType == 'investigative_submission') {
|
||||||
debugPrint("Retrying complex task 'investigative_submission' with ID $taskId.");
|
if (_marineInvestigativeService == null) return false;
|
||||||
if (_marineInvestigativeService == null) {
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
debugPrint("Retry failed: MarineInvestigativeSamplingService not initialized.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String logDirectoryPath = payload['localLogPath']; // Path to the directory
|
|
||||||
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
final file = File(jsonFilePath);
|
final file = File(jsonFilePath);
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed: Source log file no longer exists at $jsonFilePath");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final content = await file.readAsString();
|
|
||||||
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
|
||||||
final MarineInvesManualSamplingData dataToResubmit = MarineInvesManualSamplingData.fromJson(jsonData);
|
|
||||||
|
|
||||||
final result = await _marineInvestigativeService!.submitInvestigativeSample(
|
final result = await _marineInvestigativeService!.submitInvestigativeSample(
|
||||||
data: dataToResubmit,
|
data: MarineInvesManualSamplingData.fromJson(jsonDecode(await file.readAsString())),
|
||||||
appSettings: _authProvider!.appSettings,
|
appSettings: _authProvider!.appSettings,
|
||||||
authProvider: _authProvider!,
|
authProvider: _authProvider!,
|
||||||
logDirectory: logDirectoryPath,
|
logDirectory: logDirectoryPath,
|
||||||
@ -319,191 +281,377 @@ class RetryService {
|
|||||||
success = result['success'];
|
success = result['success'];
|
||||||
|
|
||||||
} else if (taskType == 'tarball_submission') {
|
} else if (taskType == 'tarball_submission') {
|
||||||
debugPrint("Retrying complex task 'tarball_submission' with ID $taskId.");
|
if (_marineTarballService == null) return false;
|
||||||
if (_marineTarballService == null) {
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
debugPrint("Retry failed: MarineTarballSamplingService not initialized.");
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
return false;
|
final file = File(jsonFilePath);
|
||||||
}
|
if (!await file.exists()) {
|
||||||
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
final String logDirectoryPath = payload['localLogPath']; // Path to the directory
|
return false;
|
||||||
|
}
|
||||||
|
final jsonData = jsonDecode(await file.readAsString());
|
||||||
|
|
||||||
|
// --- START: Manual Reconstruction of Tarball Data (Fixing .fromJson error) ---
|
||||||
|
final TarballSamplingData dataToResubmit = TarballSamplingData();
|
||||||
|
dataToResubmit.firstSampler = jsonData['firstSampler'];
|
||||||
|
dataToResubmit.firstSamplerUserId = jsonData['firstSamplerUserId'];
|
||||||
|
dataToResubmit.secondSampler = jsonData['secondSampler'];
|
||||||
|
dataToResubmit.samplingDate = jsonData['samplingDate'];
|
||||||
|
dataToResubmit.samplingTime = jsonData['samplingTime'];
|
||||||
|
dataToResubmit.selectedStateName = jsonData['selectedStateName'];
|
||||||
|
dataToResubmit.selectedCategoryName = jsonData['selectedCategoryName'];
|
||||||
|
dataToResubmit.selectedStation = jsonData['selectedStation'];
|
||||||
|
dataToResubmit.stationLatitude = jsonData['stationLatitude'];
|
||||||
|
dataToResubmit.stationLongitude = jsonData['stationLongitude'];
|
||||||
|
dataToResubmit.currentLatitude = jsonData['currentLatitude'];
|
||||||
|
dataToResubmit.currentLongitude = jsonData['currentLongitude'];
|
||||||
|
dataToResubmit.distanceDifference = (jsonData['distanceDifference'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.distanceDifferenceRemarks = jsonData['distanceDifferenceRemarks'];
|
||||||
|
dataToResubmit.classificationId = jsonData['classificationId'];
|
||||||
|
dataToResubmit.selectedClassification = jsonData['selectedClassification'];
|
||||||
|
dataToResubmit.optionalRemark1 = jsonData['optionalRemark1'];
|
||||||
|
dataToResubmit.optionalRemark2 = jsonData['optionalRemark2'];
|
||||||
|
dataToResubmit.optionalRemark3 = jsonData['optionalRemark3'];
|
||||||
|
dataToResubmit.optionalRemark4 = jsonData['optionalRemark4'];
|
||||||
|
dataToResubmit.reportId = jsonData['reportId'];
|
||||||
|
dataToResubmit.submissionStatus = jsonData['submissionStatus'];
|
||||||
|
dataToResubmit.submissionMessage = jsonData['submissionMessage'];
|
||||||
|
|
||||||
|
// Helper to create File from path if it exists
|
||||||
|
File? fileFromPath(dynamic path) => (path is String && path.isNotEmpty) ? File(path) : null;
|
||||||
|
|
||||||
|
dataToResubmit.leftCoastalViewImage = fileFromPath(jsonData['leftCoastalViewImage']);
|
||||||
|
dataToResubmit.rightCoastalViewImage = fileFromPath(jsonData['rightCoastalViewImage']);
|
||||||
|
dataToResubmit.verticalLinesImage = fileFromPath(jsonData['verticalLinesImage']);
|
||||||
|
dataToResubmit.horizontalLineImage = fileFromPath(jsonData['horizontalLineImage']);
|
||||||
|
dataToResubmit.optionalImage1 = fileFromPath(jsonData['optionalImage1']);
|
||||||
|
dataToResubmit.optionalImage2 = fileFromPath(jsonData['optionalImage2']);
|
||||||
|
dataToResubmit.optionalImage3 = fileFromPath(jsonData['optionalImage3']);
|
||||||
|
dataToResubmit.optionalImage4 = fileFromPath(jsonData['optionalImage4']);
|
||||||
|
// --- END: Manual Reconstruction ---
|
||||||
|
|
||||||
|
final result = await _marineTarballService!.submitTarballSample(
|
||||||
|
data: dataToResubmit,
|
||||||
|
appSettings: _authProvider!.appSettings,
|
||||||
|
context: null,
|
||||||
|
logDirectory: logDirectoryPath,
|
||||||
|
);
|
||||||
|
success = result['success'];
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// MARINE REPORT HANDLERS
|
||||||
|
// =======================================================================
|
||||||
|
|
||||||
|
} else if (taskType == 'marine_npe_submission' || taskType == 'npe_submission') {
|
||||||
|
if (_marineNpeService == null) return false;
|
||||||
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
final file = File(jsonFilePath);
|
final file = File(jsonFilePath);
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed: Source log file no longer exists at $jsonFilePath");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final content = await file.readAsString();
|
final content = await file.readAsString();
|
||||||
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
||||||
|
final MarineManualNpeReportData dataToResubmit = MarineManualNpeReportData();
|
||||||
|
|
||||||
// Recreate File objects from paths
|
// Reconstruction logic
|
||||||
File? fileFromJson(dynamic path) => (path is String && path.isNotEmpty) ? File(path) : null;
|
dataToResubmit.firstSamplerName = jsonData['firstSamplerName'];
|
||||||
|
dataToResubmit.firstSamplerUserId = jsonData['firstSamplerUserId'];
|
||||||
|
dataToResubmit.eventDate = jsonData['eventDate'];
|
||||||
|
dataToResubmit.eventTime = jsonData['eventTime'];
|
||||||
|
dataToResubmit.sourceOrigin = jsonData['sourceOrigin'];
|
||||||
|
dataToResubmit.locationDescription = jsonData['locationDescription'];
|
||||||
|
dataToResubmit.stateName = jsonData['stateName'];
|
||||||
|
dataToResubmit.selectedStation = jsonData['selectedStation'];
|
||||||
|
dataToResubmit.latitude = jsonData['latitude'];
|
||||||
|
dataToResubmit.longitude = jsonData['longitude'];
|
||||||
|
dataToResubmit.oxygenSaturation = jsonData['oxygenSaturation'];
|
||||||
|
dataToResubmit.electricalConductivity = jsonData['electricalConductivity'];
|
||||||
|
dataToResubmit.oxygenConcentration = jsonData['oxygenConcentration'];
|
||||||
|
dataToResubmit.turbidity = jsonData['turbidity'];
|
||||||
|
dataToResubmit.ph = jsonData['ph'];
|
||||||
|
dataToResubmit.temperature = jsonData['temperature'];
|
||||||
|
if (jsonData['fieldObservations'] != null) {
|
||||||
|
dataToResubmit.fieldObservations = Map<String, bool>.from(jsonData['fieldObservations']);
|
||||||
|
}
|
||||||
|
dataToResubmit.othersObservationRemark = jsonData['othersObservationRemark'];
|
||||||
|
dataToResubmit.possibleSource = jsonData['possibleSource'];
|
||||||
|
dataToResubmit.image1Remark = jsonData['image1Remark'];
|
||||||
|
dataToResubmit.image2Remark = jsonData['image2Remark'];
|
||||||
|
dataToResubmit.image3Remark = jsonData['image3Remark'];
|
||||||
|
dataToResubmit.image4Remark = jsonData['image4Remark'];
|
||||||
|
dataToResubmit.tarballClassificationId = jsonData['tarballClassificationId'];
|
||||||
|
dataToResubmit.selectedTarballClassification = jsonData['selectedTarballClassification'];
|
||||||
|
dataToResubmit.reportId = jsonData['reportId'];
|
||||||
|
|
||||||
final TarballSamplingData dataToResubmit = TarballSamplingData()
|
if (jsonData['npe_image_1'] != null) dataToResubmit.image1 = File(jsonData['npe_image_1']);
|
||||||
// Reconstruct the object from JSON data
|
if (jsonData['npe_image_2'] != null) dataToResubmit.image2 = File(jsonData['npe_image_2']);
|
||||||
..firstSampler = jsonData['firstSampler']
|
if (jsonData['npe_image_3'] != null) dataToResubmit.image3 = File(jsonData['npe_image_3']);
|
||||||
..firstSamplerUserId = jsonData['firstSamplerUserId']
|
if (jsonData['npe_image_4'] != null) dataToResubmit.image4 = File(jsonData['npe_image_4']);
|
||||||
..secondSampler = jsonData['secondSampler']
|
|
||||||
..samplingDate = jsonData['samplingDate']
|
|
||||||
..samplingTime = jsonData['samplingTime']
|
|
||||||
..selectedStateName = jsonData['selectedStateName']
|
|
||||||
..selectedCategoryName = jsonData['selectedCategoryName']
|
|
||||||
..selectedStation = jsonData['selectedStation']
|
|
||||||
..stationLatitude = jsonData['stationLatitude']
|
|
||||||
..stationLongitude = jsonData['stationLongitude']
|
|
||||||
..currentLatitude = jsonData['currentLatitude']
|
|
||||||
..currentLongitude = jsonData['currentLongitude']
|
|
||||||
..distanceDifference = jsonData['distanceDifference'] is num ? (jsonData['distanceDifference'] as num).toDouble() : null // Safe cast
|
|
||||||
..distanceDifferenceRemarks = jsonData['distanceDifferenceRemarks']
|
|
||||||
..classificationId = jsonData['classificationId'] is num ? (jsonData['classificationId'] as num).toInt() : null // Safe cast
|
|
||||||
..selectedClassification = jsonData['selectedClassification']
|
|
||||||
..leftCoastalViewImage = fileFromJson(jsonData['leftCoastalViewImage'])
|
|
||||||
..rightCoastalViewImage = fileFromJson(jsonData['rightCoastalViewImage'])
|
|
||||||
..verticalLinesImage = fileFromJson(jsonData['verticalLinesImage'])
|
|
||||||
..horizontalLineImage = fileFromJson(jsonData['horizontalLineImage'])
|
|
||||||
..optionalImage1 = fileFromJson(jsonData['optionalImage1'])
|
|
||||||
..optionalRemark1 = jsonData['optionalRemark1']
|
|
||||||
..optionalImage2 = fileFromJson(jsonData['optionalImage2'])
|
|
||||||
..optionalRemark2 = jsonData['optionalRemark2']
|
|
||||||
..optionalImage3 = fileFromJson(jsonData['optionalImage3'])
|
|
||||||
..optionalRemark3 = jsonData['optionalRemark3']
|
|
||||||
..optionalImage4 = fileFromJson(jsonData['optionalImage4'])
|
|
||||||
..optionalRemark4 = jsonData['optionalRemark4']
|
|
||||||
..reportId = jsonData['reportId'] // Preserve reportId if it exists
|
|
||||||
..submissionStatus = jsonData['submissionStatus'] // Preserve status info
|
|
||||||
..submissionMessage = jsonData['submissionMessage'];
|
|
||||||
|
|
||||||
|
final result = await _marineNpeService!.submitNpeReport(
|
||||||
debugPrint("Retrying Tarball submission...");
|
|
||||||
// Pass null for BuildContext, and the logDirectory path
|
|
||||||
final result = await _marineTarballService!.submitTarballSample(
|
|
||||||
data: dataToResubmit,
|
data: dataToResubmit,
|
||||||
appSettings: _authProvider!.appSettings,
|
authProvider: _authProvider!,
|
||||||
context: null, // Pass null for BuildContext during retry
|
logDirectory: logDirectoryPath,
|
||||||
logDirectory: logDirectoryPath, // Pass the directory path for potential update
|
|
||||||
);
|
);
|
||||||
success = result['success'];
|
success = result['success'];
|
||||||
|
|
||||||
// --- Simple Task Handlers ---
|
// *** START: ADDED Pre-Departure Checklist ***
|
||||||
} else if (taskType == 'api') {
|
} else if (taskType == 'pre_departure_submission') {
|
||||||
final endpoint = task['endpoint_or_path'] as String;
|
if (_marinePreDepartureService == null) return false;
|
||||||
final method = payload['method'] as String;
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
final baseUrl = await _serverConfigService.getActiveApiUrl(); // Get current active URL
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
debugPrint("Retrying API task $taskId: $method to $baseUrl/$endpoint");
|
final file = File(jsonFilePath);
|
||||||
Map<String, dynamic> result;
|
|
||||||
|
|
||||||
if (method == 'POST_MULTIPART') {
|
|
||||||
final Map<String, String> fields = Map<String, String>.from(payload['fields'] ?? {});
|
|
||||||
// Recreate File objects from paths stored in the payload
|
|
||||||
final Map<String, File> files = (payload['files'] as Map<String, dynamic>?)
|
|
||||||
?.map((key, value) => MapEntry(key, File(value as String))) ?? {};
|
|
||||||
|
|
||||||
// Check if files still exist before attempting upload
|
|
||||||
bool allFilesExist = true;
|
|
||||||
List<String> missingFiles = []; // Keep track of missing files
|
|
||||||
for (var entry in files.entries) {
|
|
||||||
File file = entry.value;
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
debugPrint("Retry failed for API task $taskId: File ${file.path} (key: ${entry.key}) no longer exists.");
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
allFilesExist = false;
|
|
||||||
missingFiles.add(entry.key);
|
|
||||||
// break; // Stop checking further if one is missing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If some files are missing, fail the entire task.
|
|
||||||
if (!allFilesExist) {
|
|
||||||
debugPrint("API Multipart retry failed for task $taskId because files are missing: ${missingFiles.join(', ')}. Removing task.");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = await _baseApiService.postMultipart(baseUrl: baseUrl, endpoint: endpoint, fields: fields, files: files);
|
final content = await file.readAsString();
|
||||||
} else { // Standard POST
|
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
||||||
final Map<String, dynamic> body = Map<String, dynamic>.from(payload['body'] ?? {});
|
final MarineManualPreDepartureChecklistData dataToResubmit = MarineManualPreDepartureChecklistData();
|
||||||
result = await _baseApiService.post(baseUrl, endpoint, body);
|
|
||||||
|
// Reconstruct Data from JSON
|
||||||
|
dataToResubmit.reporterName = jsonData['reporterName'];
|
||||||
|
dataToResubmit.reporterUserId = jsonData['reporterUserId'];
|
||||||
|
dataToResubmit.submissionDate = jsonData['submissionDate'];
|
||||||
|
dataToResubmit.location = jsonData['location'];
|
||||||
|
if (jsonData['checklistItems'] != null) {
|
||||||
|
dataToResubmit.checklistItems = Map<String, bool>.from(jsonData['checklistItems']);
|
||||||
}
|
}
|
||||||
|
if (jsonData['remarks'] != null) {
|
||||||
|
dataToResubmit.remarks = Map<String, String>.from(jsonData['remarks']);
|
||||||
|
}
|
||||||
|
dataToResubmit.reportId = jsonData['reportId'];
|
||||||
|
|
||||||
|
final result = await _marinePreDepartureService!.submitChecklist(
|
||||||
|
data: dataToResubmit,
|
||||||
|
authProvider: _authProvider!,
|
||||||
|
appSettings: _authProvider!.appSettings,
|
||||||
|
logDirectory: logDirectoryPath,
|
||||||
|
);
|
||||||
success = result['success'];
|
success = result['success'];
|
||||||
|
// *** END: ADDED Pre-Departure Checklist ***
|
||||||
|
|
||||||
|
// *** START: ADDED Sonde Calibration ***
|
||||||
|
} else if (taskType == 'sonde_calibration_submission') {
|
||||||
|
if (_marineSondeCalibrationService == null) return false;
|
||||||
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
|
final file = File(jsonFilePath);
|
||||||
|
if (!await file.exists()) {
|
||||||
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final content = await file.readAsString();
|
||||||
|
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
||||||
|
final MarineManualSondeCalibrationData dataToResubmit = MarineManualSondeCalibrationData();
|
||||||
|
|
||||||
|
// Reconstruct Data
|
||||||
|
dataToResubmit.calibratedByUserId = jsonData['calibratedByUserId'];
|
||||||
|
dataToResubmit.calibratedByUserName = jsonData['calibratedByUserName'];
|
||||||
|
dataToResubmit.sondeSerialNumber = jsonData['sondeSerialNumber'];
|
||||||
|
dataToResubmit.firmwareVersion = jsonData['firmwareVersion'];
|
||||||
|
dataToResubmit.korVersion = jsonData['korVersion'];
|
||||||
|
dataToResubmit.location = jsonData['location'];
|
||||||
|
dataToResubmit.startDateTime = jsonData['startDateTime'];
|
||||||
|
dataToResubmit.endDateTime = jsonData['endDateTime'];
|
||||||
|
|
||||||
|
// Cast to double as JSON decodes numbers as int if no decimal places
|
||||||
|
dataToResubmit.ph7Mv = (jsonData['ph_7_mv'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.ph7Before = (jsonData['ph_7_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.ph7After = (jsonData['ph_7_after'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.ph10Mv = (jsonData['ph_10_mv'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.ph10Before = (jsonData['ph_10_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.ph10After = (jsonData['ph_10_after'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.condBefore = (jsonData['cond_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.condAfter = (jsonData['cond_after'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.doBefore = (jsonData['do_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.doAfter = (jsonData['do_after'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.turbidity0Before = (jsonData['turbidity_0_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.turbidity0After = (jsonData['turbidity_0_after'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.turbidity124Before = (jsonData['turbidity_124_before'] as num?)?.toDouble();
|
||||||
|
dataToResubmit.turbidity124After = (jsonData['turbidity_124_after'] as num?)?.toDouble();
|
||||||
|
|
||||||
|
dataToResubmit.calibrationStatus = jsonData['calibration_status'];
|
||||||
|
dataToResubmit.remarks = jsonData['remarks'];
|
||||||
|
dataToResubmit.reportId = jsonData['reportId'];
|
||||||
|
|
||||||
|
final result = await _marineSondeCalibrationService!.submitCalibration(
|
||||||
|
data: dataToResubmit,
|
||||||
|
authProvider: _authProvider!,
|
||||||
|
appSettings: _authProvider!.appSettings,
|
||||||
|
logDirectory: logDirectoryPath,
|
||||||
|
);
|
||||||
|
success = result['success'];
|
||||||
|
// *** END: ADDED Sonde Calibration ***
|
||||||
|
|
||||||
|
// *** START: ADDED Equipment Maintenance ***
|
||||||
|
} else if (taskType == 'equipment_maintenance_submission') {
|
||||||
|
if (_marineEquipmentMaintenanceService == null) return false;
|
||||||
|
final String logDirectoryPath = payload['localLogPath'];
|
||||||
|
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
|
||||||
|
final file = File(jsonFilePath);
|
||||||
|
if (!await file.exists()) {
|
||||||
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final content = await file.readAsString();
|
||||||
|
final jsonData = jsonDecode(content) as Map<String, dynamic>;
|
||||||
|
final MarineManualEquipmentMaintenanceData dataToResubmit = MarineManualEquipmentMaintenanceData();
|
||||||
|
|
||||||
|
// Reconstruct Data
|
||||||
|
dataToResubmit.conductedByUserId = jsonData['conductedByUserId'];
|
||||||
|
dataToResubmit.conductedByUserName = jsonData['conductedByUserName'];
|
||||||
|
dataToResubmit.maintenanceDate = jsonData['maintenanceDate'];
|
||||||
|
dataToResubmit.lastMaintenanceDate = jsonData['lastMaintenanceDate'];
|
||||||
|
dataToResubmit.scheduleMaintenance = jsonData['scheduleMaintenance'];
|
||||||
|
dataToResubmit.isReplacement = jsonData['isReplacement'] ?? false;
|
||||||
|
dataToResubmit.timeStart = jsonData['timeStart'];
|
||||||
|
dataToResubmit.timeEnd = jsonData['timeEnd'];
|
||||||
|
dataToResubmit.location = jsonData['location'];
|
||||||
|
|
||||||
|
if (jsonData['ysiSondeChecks'] != null) {
|
||||||
|
dataToResubmit.ysiSondeChecks = Map<String, bool>.from(jsonData['ysiSondeChecks']);
|
||||||
|
}
|
||||||
|
dataToResubmit.ysiSondeComments = jsonData['ysiSondeComments'];
|
||||||
|
|
||||||
|
// Handle nested maps for Sensor Checks
|
||||||
|
if (jsonData['ysiSensorChecks'] != null) {
|
||||||
|
dataToResubmit.ysiSensorChecks = (jsonData['ysiSensorChecks'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(key, Map<String, bool>.from(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dataToResubmit.ysiSensorComments = jsonData['ysiSensorComments'];
|
||||||
|
|
||||||
|
// Handle nested maps for Replacements
|
||||||
|
if (jsonData['ysiReplacements'] != null) {
|
||||||
|
dataToResubmit.ysiReplacements = (jsonData['ysiReplacements'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(key, Map<String, String>.from(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonData['vanDornChecks'] != null) {
|
||||||
|
dataToResubmit.vanDornChecks = (jsonData['vanDornChecks'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(key, Map<String, bool>.from(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dataToResubmit.vanDornComments = jsonData['vanDornComments'];
|
||||||
|
dataToResubmit.vanDornCurrentSerial = jsonData['vanDornCurrentSerial'];
|
||||||
|
dataToResubmit.vanDornNewSerial = jsonData['vanDornNewSerial'];
|
||||||
|
|
||||||
|
if (jsonData['vanDornReplacements'] != null) {
|
||||||
|
dataToResubmit.vanDornReplacements = (jsonData['vanDornReplacements'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(key, Map<String, String>.from(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dataToResubmit.reportId = jsonData['reportId'];
|
||||||
|
|
||||||
|
final result = await _marineEquipmentMaintenanceService!.submitMaintenanceReport(
|
||||||
|
data: dataToResubmit,
|
||||||
|
authProvider: _authProvider!,
|
||||||
|
appSettings: _authProvider!.appSettings,
|
||||||
|
logDirectory: logDirectoryPath,
|
||||||
|
);
|
||||||
|
success = result['success'];
|
||||||
|
// *** END: ADDED Equipment Maintenance ***
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// SIMPLE API / FTP HANDLERS
|
||||||
|
// =======================================================================
|
||||||
|
|
||||||
|
} else if (taskType == 'api') {
|
||||||
|
final endpoint = task['endpoint_or_path'] as String;
|
||||||
|
final method = payload['method'] as String;
|
||||||
|
final baseUrl = await _serverConfigService.getActiveApiUrl();
|
||||||
|
|
||||||
|
if (method == 'POST_MULTIPART') {
|
||||||
|
final Map<String, String> fields = Map<String, String>.from(payload['fields'] ?? {});
|
||||||
|
final Map<String, File> files = (payload['files'] as Map<String, dynamic>?)
|
||||||
|
?.map((key, value) => MapEntry(key, File(value as String))) ?? {};
|
||||||
|
|
||||||
|
bool allFilesExist = true;
|
||||||
|
for (var entry in files.entries) {
|
||||||
|
if (!await entry.value.exists()) {
|
||||||
|
allFilesExist = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allFilesExist) {
|
||||||
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final result = await _baseApiService.postMultipart(baseUrl: baseUrl, endpoint: endpoint, fields: fields, files: files);
|
||||||
|
success = result['success'];
|
||||||
|
} else {
|
||||||
|
final Map<String, dynamic> body = Map<String, dynamic>.from(payload['body'] ?? {});
|
||||||
|
final result = await _baseApiService.post(baseUrl, endpoint, body);
|
||||||
|
success = result['success'];
|
||||||
|
}
|
||||||
|
|
||||||
} else if (taskType == 'ftp') {
|
} else if (taskType == 'ftp') {
|
||||||
final remotePath = task['endpoint_or_path'] as String;
|
final remotePath = task['endpoint_or_path'] as String;
|
||||||
final localFile = File(payload['localFilePath'] as String);
|
final localFile = File(payload['localFilePath'] as String);
|
||||||
final int? ftpConfigId = payload['ftpConfigId'] as int?;
|
final int? ftpConfigId = payload['ftpConfigId'] as int?;
|
||||||
|
|
||||||
debugPrint("Retrying FTP task $taskId: Uploading ${localFile.path} to $remotePath using config ID $ftpConfigId");
|
|
||||||
|
|
||||||
if (ftpConfigId == null) {
|
if (ftpConfigId == null) {
|
||||||
debugPrint("Retry failed for FTP task $taskId: Missing FTP configuration ID in payload.");
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task
|
return false;
|
||||||
|
}
|
||||||
|
if (await localFile.exists()) {
|
||||||
|
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
|
||||||
|
final config = ftpConfigs.firstWhere((c) => c['ftp_config_id'] == ftpConfigId, orElse: () => <String, dynamic>{});
|
||||||
|
if (config.isEmpty) return false;
|
||||||
|
|
||||||
|
final result = await _ftpService.uploadFile(config: config, fileToUpload: localFile, remotePath: remotePath);
|
||||||
|
success = result['success'];
|
||||||
|
} else {
|
||||||
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await localFile.exists()) {
|
|
||||||
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
|
|
||||||
final config = ftpConfigs.firstWhere((c) => c['ftp_config_id'] == ftpConfigId, orElse: () => <String, dynamic>{}); // Use explicit type
|
|
||||||
|
|
||||||
|
|
||||||
if (config.isEmpty) {
|
|
||||||
debugPrint("Retry failed for FTP task $taskId: FTP configuration with ID $ftpConfigId not found.");
|
|
||||||
return false; // Fail the retry attempt, keep in queue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt upload using the specific config
|
|
||||||
final result = await _ftpService.uploadFile(config: config, fileToUpload: localFile, remotePath: remotePath);
|
|
||||||
success = result['success'];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
debugPrint("Retry failed for FTP task $taskId: Source file no longer exists at ${localFile.path}");
|
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId); // Remove task if file is gone
|
|
||||||
return false; // Explicitly return false as success is false
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
debugPrint("Unknown task type '$taskType' for task ID $taskId. Cannot retry. Removing task.");
|
debugPrint("Unknown task type '$taskType' for task ID $taskId. Cannot retry. Removing task.");
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
} on SessionExpiredException catch (e) {
|
} on SessionExpiredException catch (e) {
|
||||||
debugPrint("Session expired during retry attempt for task $taskId (Type: $taskType): $e. Task remains in queue.");
|
debugPrint("Session expired during retry attempt: $e");
|
||||||
success = false; // Session expiry during retry means failure for this attempt
|
success = false;
|
||||||
} catch (e, stacktrace) { // Catch potential exceptions during processing
|
} catch (e, stacktrace) {
|
||||||
debugPrint("A critical error occurred while retrying task $taskId (Type: $taskType): $e");
|
debugPrint("A critical error occurred while retrying task $taskId: $e");
|
||||||
debugPrint("Stacktrace: $stacktrace"); // Log stacktrace for detailed debugging
|
debugPrint("Stacktrace: $stacktrace");
|
||||||
success = false; // Ensure success is false on exception
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-processing: Remove successful tasks from queue
|
|
||||||
if (success) {
|
if (success) {
|
||||||
debugPrint("Task $taskId (Type: $taskType) completed successfully. Removing from queue.");
|
debugPrint("Task $taskId (Type: $taskType) completed successfully. Removing from queue.");
|
||||||
await _dbHelper.deleteRequestFromQueue(taskId);
|
await _dbHelper.deleteRequestFromQueue(taskId);
|
||||||
// If it was a complex task involving temporary ZIP files, attempt to delete them
|
|
||||||
if (taskType.endsWith('_submission') && payload['localLogPath'] != null) {
|
if (taskType.endsWith('_submission') && payload['localLogPath'] != null) {
|
||||||
// Assume localLogPath points to the JSON file, get directory for cleanup
|
|
||||||
String pathToCheck = payload['localLogPath'];
|
String pathToCheck = payload['localLogPath'];
|
||||||
// Check if it's a directory path already (for older marine insitu logs)
|
|
||||||
bool isDirectory = await Directory(pathToCheck).exists();
|
bool isDirectory = await Directory(pathToCheck).exists();
|
||||||
if (!isDirectory && pathToCheck.endsWith('.json')) {
|
if (!isDirectory && pathToCheck.endsWith('.json')) {
|
||||||
pathToCheck = p.dirname(pathToCheck); // Get directory if it's a file path
|
pathToCheck = p.dirname(pathToCheck);
|
||||||
isDirectory = true; // Now we are checking the directory
|
isDirectory = true;
|
||||||
}
|
}
|
||||||
_cleanUpTemporaryZipFiles(pathToCheck, isDirectory: isDirectory);
|
_cleanUpTemporaryZipFiles(pathToCheck, isDirectory: isDirectory);
|
||||||
}
|
}
|
||||||
// If it was an FTP task, attempt to delete the temporary ZIP file
|
|
||||||
if (taskType == 'ftp' && payload['localFilePath'] != null && (payload['localFilePath'] as String).endsWith('.zip')) {
|
if (taskType == 'ftp' && payload['localFilePath'] != null && (payload['localFilePath'] as String).endsWith('.zip')) {
|
||||||
_cleanUpTemporaryZipFiles(payload['localFilePath'], isDirectory: false);
|
_cleanUpTemporaryZipFiles(payload['localFilePath'], isDirectory: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
debugPrint("Retry attempt for task $taskId (Type: $taskType) failed. It will remain in the queue.");
|
debugPrint("Retry attempt for task $taskId (Type: $taskType) failed. It will remain in the queue.");
|
||||||
// Optional: Implement a retry limit here. If retries > X, mark task as 'failed' instead of 'pending'.
|
|
||||||
// e.g., await _dbHelper.updateTaskStatus(taskId, 'failed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to delete temporary zip files after successful retry.
|
|
||||||
void _cleanUpTemporaryZipFiles(String path, {required bool isDirectory}) async {
|
void _cleanUpTemporaryZipFiles(String path, {required bool isDirectory}) async {
|
||||||
try {
|
try {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
@ -511,31 +659,17 @@ class RetryService {
|
|||||||
if (await dir.exists()) {
|
if (await dir.exists()) {
|
||||||
final filesInDir = dir.listSync();
|
final filesInDir = dir.listSync();
|
||||||
for (var entity in filesInDir) {
|
for (var entity in filesInDir) {
|
||||||
// Delete only ZIP files within the log directory
|
|
||||||
if (entity is File && entity.path.endsWith('.zip')) {
|
if (entity is File && entity.path.endsWith('.zip')) {
|
||||||
debugPrint("Deleting temporary zip file from directory: ${entity.path}");
|
debugPrint("Deleting temporary zip file from directory: ${entity.path}");
|
||||||
await entity.delete();
|
await entity.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Optional: Delete the directory itself if now empty, ONLY if safe.
|
|
||||||
// Be cautious as data.json might still be needed or other files exist.
|
|
||||||
// if (await dir.listSync().isEmpty) {
|
|
||||||
// await dir.delete();
|
|
||||||
// debugPrint("Deleted empty log directory: ${dir.path}");
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
debugPrint("Log directory not found for cleanup: $path");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If it's a specific file path (like from FTP task)
|
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
if (await file.exists() && path.endsWith('.zip')) { // Ensure it's a zip file
|
if (await file.exists() && path.endsWith('.zip')) {
|
||||||
debugPrint("Deleting temporary zip file: ${file.path}");
|
debugPrint("Deleting temporary zip file: ${file.path}");
|
||||||
await file.delete();
|
await file.delete();
|
||||||
} else if (!path.endsWith('.zip')) {
|
|
||||||
debugPrint("Skipping cleanup for non-zip file path: $path");
|
|
||||||
} else {
|
|
||||||
debugPrint("Temporary zip file not found for cleanup: $path");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -543,4 +677,4 @@ class RetryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End of RetryService class
|
}
|
||||||
@ -540,61 +540,24 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- START: NEW HELPER METHOD (for timestamp ID) ---
|
// --- START: NEW HELPER METHOD (for timestamp ID) ---
|
||||||
/// Generates the specific Telegram alert message content for River Investigative.
|
/// Generates a unique timestamp ID from the sampling date and time.
|
||||||
Future<String> _generateInvestigativeAlertMessage(RiverInvesManualSamplingData data, {required bool isDataOnly}) async { // Updated model type
|
// Note: This function was duplicated. The duplicate has been removed.
|
||||||
final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)";
|
// The first occurrence of this function is kept, even though the error message pointed to it.
|
||||||
// Use helpers to get determined names/codes
|
// Keeping this one:
|
||||||
final stationName = data.getDeterminedRiverName() ?? data.getDeterminedStationName() ?? 'N/A'; // Combine river/station name
|
/*
|
||||||
final stationCode = data.getDeterminedStationCode() ?? 'N/A';
|
String _generateTimestampId(String? date, String? time) {
|
||||||
final submissionDate = data.samplingDate ?? DateFormat('yyyy-MM-dd').format(DateTime.now());
|
final String dateTimeString = "${date ?? ''} ${time ?? ''}";
|
||||||
final submitter = data.firstSamplerName ?? 'N/A';
|
try {
|
||||||
final sondeID = data.sondeId ?? 'N/A';
|
// Time format from model is HH:mm
|
||||||
final distanceKm = data.distanceDifferenceInKm ?? 0;
|
final DateTime samplingDateTime = DateFormat('yyyy-MM-dd HH:mm').parse(dateTimeString);
|
||||||
final distanceMeters = (distanceKm * 1000).toStringAsFixed(0);
|
return samplingDateTime.millisecondsSinceEpoch.toString();
|
||||||
final distanceRemarks = data.distanceDifferenceRemarks ?? ''; // Default to empty string
|
} catch (e) {
|
||||||
|
// Fallback: if parsing fails, use the current time in milliseconds
|
||||||
final buffer = StringBuffer()
|
debugPrint("Could not parse '$dateTimeString' for timestamp ID, using current time. Error: $e");
|
||||||
..writeln('✅ *River Investigative Sample ${submissionType} Submitted:*') // Updated title
|
return DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
..writeln();
|
|
||||||
|
|
||||||
// Adapt station info based on type
|
|
||||||
buffer.writeln('*Station Type:* ${data.stationTypeSelection ?? 'N/A'}');
|
|
||||||
if (data.stationTypeSelection == 'New Location') {
|
|
||||||
buffer.writeln('*New Location Name:* ${data.newStationName ?? 'N/A'}');
|
|
||||||
buffer.writeln('*New Location Code:* ${data.newStationCode ?? 'N/A'}');
|
|
||||||
buffer.writeln('*New Location State:* ${data.newStateName ?? 'N/A'}');
|
|
||||||
buffer.writeln('*New Location Basin:* ${data.newBasinName ?? 'N/A'}');
|
|
||||||
buffer.writeln('*New Location River:* ${data.newRiverName ?? 'N/A'}');
|
|
||||||
buffer.writeln('*Coordinates:* ${data.stationLatitude ?? 'N/A'}, ${data.stationLongitude ?? 'N/A'}');
|
|
||||||
} else {
|
|
||||||
buffer.writeln('*Station Name & Code:* $stationName ($stationCode)');
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
|
||||||
..writeln('*Date of Submitted:* $submissionDate')
|
|
||||||
..writeln('*Submitted by User:* $submitter')
|
|
||||||
..writeln('*Sonde ID:* $sondeID')
|
|
||||||
..writeln('*Status of Submission:* Successful');
|
|
||||||
|
|
||||||
// Include distance warning only if NOT a new location and distance > 50m
|
|
||||||
if (data.stationTypeSelection != 'New Location' && (distanceKm * 1000 > 50 || distanceRemarks.isNotEmpty)) {
|
|
||||||
buffer
|
|
||||||
..writeln()
|
|
||||||
..writeln('🔔 *Distance Alert:*')
|
|
||||||
..writeln('*Distance from station:* $distanceMeters meters');
|
|
||||||
if (distanceRemarks.isNotEmpty) {
|
|
||||||
buffer.writeln('*Remarks for distance:* $distanceRemarks');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Add parameter limit check section (uses the same river limits)
|
|
||||||
final outOfBoundsAlert = await _getOutOfBoundsAlertSection(data); // Call helper
|
|
||||||
if (outOfBoundsAlert.isNotEmpty) {
|
|
||||||
buffer.write(outOfBoundsAlert);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
// --- END: NEW HELPER METHOD ---
|
// --- END: NEW HELPER METHOD ---
|
||||||
|
|
||||||
// --- START: MODIFIED _generateBaseFileName ---
|
// --- START: MODIFIED _generateBaseFileName ---
|
||||||
@ -635,6 +598,7 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
|
|
||||||
// 4. CREATE DATA ZIP
|
// 4. CREATE DATA ZIP
|
||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
|
// --- START FIX: Include all four JSON files ---
|
||||||
jsonDataMap: {
|
jsonDataMap: {
|
||||||
// *** MODIFIED: Use Investigative model's JSON methods and filenames ***
|
// *** MODIFIED: Use Investigative model's JSON methods and filenames ***
|
||||||
'db.json': data.toDbJson(), // Main data structure
|
'db.json': data.toDbJson(), // Main data structure
|
||||||
@ -642,6 +606,7 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
'river_inves_reading.json': data.toReadingJson(),
|
'river_inves_reading.json': data.toReadingJson(),
|
||||||
'river_inves_manual_info.json': data.toManualInfoJson(),
|
'river_inves_manual_info.json': data.toManualInfoJson(),
|
||||||
},
|
},
|
||||||
|
// --- END FIX ---
|
||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
||||||
);
|
);
|
||||||
@ -786,7 +751,9 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
/// Handles sending or queuing the Telegram alert for River Investigative submissions.
|
/// Handles sending or queuing the Telegram alert for River Investigative submissions.
|
||||||
Future<void> _handleSuccessAlert(RiverInvesManualSamplingData data, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly, bool isSessionExpired = false}) async { // Updated model type
|
Future<void> _handleSuccessAlert(RiverInvesManualSamplingData data, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly, bool isSessionExpired = false}) async { // Updated model type
|
||||||
try {
|
try {
|
||||||
final message = await _generateInvestigativeAlertMessage(data, isDataOnly: isDataOnly); // Call specific helper
|
// --- FIX: Correct function name to the defined helper method ---
|
||||||
|
final message = await _generateSuccessAlertMessage(data, isDataOnly: isDataOnly); // Call specific helper
|
||||||
|
// --- END FIX ---
|
||||||
// *** MODIFIED: Telegram key ***
|
// *** MODIFIED: Telegram key ***
|
||||||
final alertKey = 'river_investigative'; // Specific key for this module
|
final alertKey = 'river_investigative'; // Specific key for this module
|
||||||
|
|
||||||
|
|||||||
@ -308,6 +308,35 @@ class RiverManualTriennialSamplingService {
|
|||||||
await _retryService.addApiToQueue(endpoint: 'river/triennial/images', method: 'POST_MULTIPART', fields: {'r_tri_id': apiRecordId}, files: finalImageFiles);
|
await _retryService.addApiToQueue(endpoint: 'river/triennial/images', method: 'POST_MULTIPART', fields: {'r_tri_id': apiRecordId}, files: finalImageFiles);
|
||||||
// --- END: MODIFIED TO USE TIMESTAMP ID ---
|
// --- END: MODIFIED TO USE TIMESTAMP ID ---
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START FIX: Queue all four JSON files ---
|
||||||
|
// Get all potential FTP configs
|
||||||
|
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
|
||||||
|
|
||||||
|
final dataZip = await _zippingService.createDataZip(
|
||||||
|
jsonDataMap: { // Use specific JSON structures for River Triennial FTP
|
||||||
|
'db.json': data.toDbJson(), // Assuming similar structure is needed, adjust if different
|
||||||
|
'river_triennial_basic_form.json': data.toBasicFormJson(),
|
||||||
|
'river_triennial_reading.json': data.toReadingJson(),
|
||||||
|
'river_triennial_manual_info.json': data.toManualInfoJson(),
|
||||||
|
},
|
||||||
|
baseFileName: _generateBaseFileName(data),
|
||||||
|
destinationDir: null,
|
||||||
|
);
|
||||||
|
if (dataZip != null) {
|
||||||
|
// Queue for each config separately
|
||||||
|
for (final config in ftpConfigs) {
|
||||||
|
final configId = config['ftp_config_id'];
|
||||||
|
if (configId != null) {
|
||||||
|
await _retryService.addFtpToQueue(
|
||||||
|
localFilePath: dataZip.path,
|
||||||
|
remotePath: '/${p.basename(dataZip.path)}',
|
||||||
|
ftpConfigId: configId // Provide the specific config ID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- END FIX ---
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Submit FTP Files
|
// 3. Submit FTP Files
|
||||||
@ -327,7 +356,9 @@ class RiverManualTriennialSamplingService {
|
|||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
jsonDataMap: { // Use specific JSON structures for River Triennial FTP
|
jsonDataMap: { // Use specific JSON structures for River Triennial FTP
|
||||||
'db.json': data.toDbJson(), // Assuming similar structure is needed, adjust if different
|
'db.json': data.toDbJson(), // Assuming similar structure is needed, adjust if different
|
||||||
// Add other JSON files if required for Triennial FTP
|
'river_triennial_basic_form.json': data.toBasicFormJson(), // ADDED
|
||||||
|
'river_triennial_reading.json': data.toReadingJson(), // ADDED
|
||||||
|
'river_triennial_manual_info.json': data.toManualInfoJson(), // ADDED
|
||||||
},
|
},
|
||||||
baseFileName: baseFileNameForQueue,
|
baseFileName: baseFileNameForQueue,
|
||||||
destinationDir: null,
|
destinationDir: null,
|
||||||
@ -537,7 +568,14 @@ class RiverManualTriennialSamplingService {
|
|||||||
|
|
||||||
// 4. CREATE 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
|
// --- START FIX: Include all four JSON files ---
|
||||||
|
jsonDataMap: {
|
||||||
|
'db.json': data.toDbJson(),
|
||||||
|
'river_triennial_basic_form.json': data.toBasicFormJson(), // ADDED
|
||||||
|
'river_triennial_reading.json': data.toReadingJson(), // ADDED
|
||||||
|
'river_triennial_manual_info.json': data.toManualInfoJson(), // ADDED
|
||||||
|
},
|
||||||
|
// --- END FIX ---
|
||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir,
|
destinationDir: localSubmissionDir,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
// lib/services/settings_service.dart
|
||||||
|
|
||||||
// No longer needs SharedPreferences or BaseApiService for its core logic.
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class SettingsService {
|
class SettingsService {
|
||||||
// The service no longer manages its own state or makes API calls.
|
// The service no longer manages its own state or makes API calls.
|
||||||
@ -32,43 +32,55 @@ class SettingsService {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the Marine In-Situ module from the provided settings list.
|
/// Gets the Chat ID for the Marine In-Situ module.
|
||||||
String getInSituChatId(List<Map<String, dynamic>>? settings) {
|
String getInSituChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'marine_in_situ');
|
return _getChatId(settings, 'marine_in_situ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the Tarball module from the provided settings list.
|
/// Gets the Chat ID for the Tarball module.
|
||||||
String getTarballChatId(List<Map<String, dynamic>>? settings) {
|
String getTarballChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'marine_tarball');
|
return _getChatId(settings, 'marine_tarball');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the River In-Situ module from the provided settings list.
|
/// Gets the Chat ID for the Marine Investigative module.
|
||||||
|
String getMarineInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
||||||
|
return _getChatId(settings, 'marine_investigative');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADDED: Getter for Marine NPE Report ---
|
||||||
|
String getMarineReportChatId(List<Map<String, dynamic>>? settings) {
|
||||||
|
return _getChatId(settings, 'marine_report');
|
||||||
|
}
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
/// Gets the Chat ID for the River In-Situ module.
|
||||||
String getRiverInSituChatId(List<Map<String, dynamic>>? settings) {
|
String getRiverInSituChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'river_in_situ');
|
return _getChatId(settings, 'river_in_situ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the River Triennial module from the provided settings list.
|
/// Gets the Chat ID for the River Triennial module.
|
||||||
String getRiverTriennialChatId(List<Map<String, dynamic>>? settings) {
|
String getRiverTriennialChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'river_triennial');
|
return _getChatId(settings, 'river_triennial');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the River Investigative module from the provided settings list.
|
/// Gets the Chat ID for the River Investigative module.
|
||||||
String getRiverInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
String getRiverInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'river_investigative');
|
return _getChatId(settings, 'river_investigative');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the Air Manual module from the provided settings list.
|
// --- ADDED: Getter for River NPE Report ---
|
||||||
|
String getRiverReportChatId(List<Map<String, dynamic>>? settings) {
|
||||||
|
return _getChatId(settings, 'river_report');
|
||||||
|
}
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
/// Gets the Chat ID for the Air Manual module.
|
||||||
String getAirManualChatId(List<Map<String, dynamic>>? settings) {
|
String getAirManualChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'air_manual');
|
return _getChatId(settings, 'air_manual');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the Air Investigative module from the provided settings list.
|
/// Gets the Chat ID for the Air Investigative module.
|
||||||
String getAirInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
String getAirInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
||||||
return _getChatId(settings, 'air_investigative');
|
return _getChatId(settings, 'air_investigative');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Chat ID for the Marine Investigative module from the provided settings list.
|
|
||||||
String getMarineInvestigativeChatId(List<Map<String, dynamic>>? settings) {
|
|
||||||
return _getChatId(settings, 'marine_investigative');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user