You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/export_module/provider/export_provider.dart

395 lines
12 KiB
Dart

import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/modules/export_module/models/export_form_model.dart';
import 'package:test_sa/modules/export_module/models/export_module_type.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
class ExportApiProvider extends ChangeNotifier {
bool _isExporting = false;
bool get isExporting => _isExporting;
int? _stateCode;
int? get stateCode => _stateCode;
String? _errorMessage;
String? get errorMessage => _errorMessage;
Future<String?> exportData<T>(ExportRequest<T> exportRequest, ExportModuleType moduleType, BuildContext context) async {
_isExporting = true;
_errorMessage = null;
notifyListeners();
ProgressDialog pd = ProgressDialog(context: context);
pd.show(
max: 100,
msg: 'Preparing export...',
progressBgColor: Colors.transparent,
progressValueColor: AppColor.primary10,
msgFontWeight: FontWeight.normal,
// backgroundColor: AppColor.background(context),
// msgColor: AppColor.headingTextColor(context),
valueColor: AppColor.primary10,
);
IOSink? sink;
File? outputFile;
try {
final String apiUrl = _getApiUrlForModule(moduleType);
final Map<String, dynamic> body = _buildRequestBody(exportRequest, moduleType);
if (!context.mounted) return null;
pd.update(msg: 'Sending request to server...');
log('📡 Sending export request...');
final startTime = DateTime.now();
final streamedResponse = await ApiManager.instance.postStreaming(apiUrl, body: body);
_stateCode = streamedResponse.statusCode;
final responseTime = DateTime.now().difference(startTime).inMilliseconds;
log('✅ Response received in ${responseTime}ms - Status: ${streamedResponse.statusCode}');
if (streamedResponse.statusCode >= 200 && streamedResponse.statusCode < 300) {
final contentLength = streamedResponse.contentLength ?? 0;
log('📊 Content length: ${contentLength > 0 ? "${(contentLength / 1024 / 1024).toStringAsFixed(2)} MB" : "Unknown"}');
if (!context.mounted) {
await streamedResponse.stream.drain();
return null;
}
final String ext = _getFileExtension(exportRequest.format);
final String dir = (await getApplicationDocumentsDirectory()).path;
final String filePath = "$dir/${DateTime.now().millisecondsSinceEpoch}.$ext";
outputFile = File(filePath);
sink = outputFile.openWrite();
int received = 0;
int lastProgress = 15;
final downloadStartTime = DateTime.now();
pd.update(value: 15, msg: 'Exporting...');
await for (var chunk in streamedResponse.stream) {
sink.add(chunk);
received += chunk.length;
if (!context.mounted) {
await sink.close();
await outputFile.delete();
return null;
}
if (contentLength > 0) {
final progress = (15 + ((received / contentLength) * 70)).clamp(15, 85).toInt();
if (progress > lastProgress) {
lastProgress = progress;
final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2);
final totalMB = (contentLength / 1024 / 1024).toStringAsFixed(2);
pd.update(
value: progress,
msg: 'Exporting: $downloadedMB MB / $totalMB MB',
);
}
} else {
final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2);
pd.update(
msg: 'Exporting: $downloadedMB MB',
);
}
}
await sink.close();
sink = null;
final downloadTime = DateTime.now().difference(downloadStartTime).inMilliseconds;
final fileSizeMB = (received / 1024 / 1024).toStringAsFixed(2);
final downloadSpeed = downloadTime > 0 ? ((received / 1024 / 1024) / (downloadTime / 1000)).toStringAsFixed(2) : 'N/A';
if (!context.mounted) {
await outputFile.delete();
return null;
}
pd.close();
final totalTime = DateTime.now().difference(startTime).inMilliseconds;
return filePath;
} else {
if (context.mounted) {
pd.close();
}
try {
final responseBody = await streamedResponse.stream.bytesToString();
final responseData = json.decode(responseBody);
_errorMessage = responseData['message'] ?? 'Export failed';
} catch (e) {
log('❌ Error parsing error response: $e');
_errorMessage = 'Export failed with status: ${streamedResponse.statusCode}';
}
return null;
}
} catch (error, stackTrace) {
if (sink != null) {
try {
await sink.close();
} catch (e) {
log('⚠️ Error closing sink: $e');
}
}
if (outputFile != null && await outputFile.exists()) {
try {
await outputFile.delete();
log('🗑️ Deleted incomplete file');
} catch (e) {
log('⚠️ Error deleting incomplete file: $e');
}
}
if (context.mounted) {
pd.close();
}
log('❌ Error exporting data: $error');
_stateCode = -1;
_errorMessage = 'An error occurred during export: $error';
return null;
} finally {
_isExporting = false;
notifyListeners();
log('🔚 Export process finished');
}
}
String _getApiUrlForModule(ExportModuleType moduleType) {
switch (moduleType) {
case ExportModuleType.asset:
return URLs.exportAsset;
case ExportModuleType.callRequest:
return URLs.exportAllWorkOrder;
default:
return URLs.exportAsset;
}
}
String _getFileExtension(String format) {
final formatLower = format.toLowerCase();
switch (formatLower) {
case 'excel':
return 'xlsx';
case 'pdf':
return 'pdf';
case 'word':
case 'doc':
return 'docx';
case 'text':
case 'txt':
return 'txt';
case 'csv':
return 'csv';
default:
return formatLower;
}
}
Map<String, dynamic> _buildRequestBody<T>(ExportRequest<T> exportRequest, ExportModuleType moduleType) {
final Map<String, dynamic> body = {};
if (exportRequest.searchModel != null) {
try {
final searchModelJson = (exportRequest.searchModel as dynamic).toJson() as Map<String, dynamic>;
body.addAll(searchModelJson);
} catch (e) {
body['searchModel'] = exportRequest.searchModel;
}
}
if (moduleType == ExportModuleType.callRequest) {
_addCallRequestFields(body);
}
body['pageNo'] = exportRequest.pageNo;
body['pageSize'] = exportRequest.pageSize;
body['exportRequest'] = {
'selectedColumns': exportRequest.selectedColumns,
'format': exportRequest.format,
};
return body;
}
void _addCallRequestFields(Map<String, dynamic> body) {
if (!body.containsKey('typeTransaction') || (body['typeTransaction'] as List?)?.isEmpty == true) {
body['typeTransaction'] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
}
if (!body.containsKey('statusTransaction') || (body['statusTransaction'] as List?)?.isEmpty == true) {
body['statusTransaction'] = [1, 2, 4];
}
if (!body.containsKey('priority')) {
body['priority'] = [];
}
if (!body.containsKey('displayData')) {
body['displayData'] = [];
}
}
void resetError() {
_errorMessage = null;
notifyListeners();
}
/// Print work order data as PDF
Future<String?> printWorkOrder(int workOrderId, int moduleId, BuildContext context) async {
_isExporting = true;
_errorMessage = null;
notifyListeners();
ProgressDialog pd = ProgressDialog(context: context);
pd.show(
max: 100,
msg: 'Preparing print...',
progressBgColor: Colors.transparent,
progressValueColor: AppColor.primary10,
msgFontWeight: FontWeight.normal,
valueColor: AppColor.primary10,
);
IOSink? sink;
File? outputFile;
try {
if (!context.mounted) return null;
pd.update(msg: 'Sending print request...');
log('📡 Sending print request...');
final startTime = DateTime.now();
// Prepare form data
final Map<String, String> formData = {
'properties': json.encode({
'workOrderId': workOrderId,
'moduleId': moduleId,
}),
};
final streamedResponse = await ApiManager.instance.postFormDataStreaming(
URLs.generateReport,
body: formData
);
_stateCode = streamedResponse.statusCode;
final responseTime = DateTime.now().difference(startTime).inMilliseconds;
log('✅ Print response received in ${responseTime}ms - Status: ${streamedResponse.statusCode}');
if (streamedResponse.statusCode >= 200 && streamedResponse.statusCode < 300) {
final contentLength = streamedResponse.contentLength ?? 0;
log('📊 Content length: ${contentLength > 0 ? "${(contentLength / 1024 / 1024).toStringAsFixed(2)} MB" : "Unknown"}');
if (!context.mounted) {
await streamedResponse.stream.drain();
return null;
}
final String dir = (await getApplicationDocumentsDirectory()).path;
final String filePath = "$dir/print_${DateTime.now().millisecondsSinceEpoch}.pdf";
outputFile = File(filePath);
sink = outputFile.openWrite();
int received = 0;
int lastProgress = 15;
final downloadStartTime = DateTime.now();
pd.update(value: 15, msg: 'Generating PDF...');
await for (var chunk in streamedResponse.stream) {
sink.add(chunk);
received += chunk.length;
if (!context.mounted) {
await sink.close();
await outputFile.delete();
return null;
}
if (contentLength > 0) {
final progress = (15 + ((received / contentLength) * 70)).clamp(15, 85).toInt();
if (progress > lastProgress) {
lastProgress = progress;
final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2);
final totalMB = (contentLength / 1024 / 1024).toStringAsFixed(2);
pd.update(
value: progress,
msg: 'Generating PDF: $downloadedMB MB / $totalMB MB',
);
}
} else {
final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2);
pd.update(
msg: 'Generating PDF: $downloadedMB MB',
);
}
}
await sink.close();
sink = null;
final downloadTime = DateTime.now().difference(downloadStartTime).inMilliseconds;
final fileSizeMB = (received / 1024 / 1024).toStringAsFixed(2);
log('✅ PDF generated successfully - Size: ${fileSizeMB} MB');
if (!context.mounted) {
await outputFile.delete();
return null;
}
pd.close();
return filePath;
} else {
if (context.mounted) {
pd.close();
}
try {
final responseBody = await streamedResponse.stream.bytesToString();
final responseData = json.decode(responseBody);
_errorMessage = responseData['message'] ?? 'Print failed';
} catch (e) {
log('❌ Error parsing print error response: $e');
_errorMessage = 'Print failed with status: ${streamedResponse.statusCode}';
}
return null;
}
} catch (error, stackTrace) {
if (sink != null) {
try {
await sink.close();
} catch (e) {
log('⚠️ Error closing sink: $e');
}
}
if (outputFile != null && await outputFile.exists()) {
try {
await outputFile.delete();
log('🗑️ Deleted incomplete file');
} catch (e) {
log('⚠️ Error deleting incomplete file: $e');
}
}
if (context.mounted) {
pd.close();
}
log('❌ Error printing data: $error');
_stateCode = -1;
_errorMessage = 'An error occurred during print: $error';
return null;
} finally {
_isExporting = false;
notifyListeners();
log('🔚 Print process finished');
}
}
}