feat: Enhance image handling in task reporting and commenting with compression and content type detection

This commit is contained in:
Vaibhav Surve 2025-06-12 18:00:31 +05:30
parent 3a449441fa
commit 1f784d96f4
2 changed files with 117 additions and 43 deletions

View File

@ -10,6 +10,7 @@ import 'package:marco/controller/task_planing/daily_task_planing_controller.dart
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
import 'package:marco/helpers/widgets/my_image_compressor.dart';
final Logger logger = Logger(); final Logger logger = Logger();
@ -136,14 +137,27 @@ class ReportTaskController extends MyController {
isLoading.value = true; isLoading.value = true;
List<Map<String, dynamic>>? imageData; List<Map<String, dynamic>>? imageData;
if (images != null && images.isNotEmpty) { if (images != null && images.isNotEmpty) {
imageData = await Future.wait(images.map((file) async { if (images != null && images.isNotEmpty) {
final bytes = await file.readAsBytes(); final imageFutures = images.map((file) async {
final base64Image = base64Encode(bytes); final compressedBytes = await compressImageToUnder100KB(file);
return { if (compressedBytes == null) return null;
"fileName": file.path.split('/').last,
"fileData": base64Image, final base64Image = base64Encode(compressedBytes);
}; final fileName = file.path.split('/').last;
})); final contentType = _getContentTypeFromFileName(fileName);
return {
"fileName": fileName,
"base64Data": base64Image,
"contentType": contentType,
"fileSize": compressedBytes.lengthInBytes,
"description": "Image uploaded for task report",
};
}).toList();
final results = await Future.wait(imageFutures);
imageData = results.whereType<Map<String, dynamic>>().toList();
}
} }
final success = await ApiService.reportTask( final success = await ApiService.reportTask(
@ -180,6 +194,23 @@ class ReportTaskController extends MyController {
} }
} }
String _getContentTypeFromFileName(String fileName) {
final ext = fileName.split('.').last.toLowerCase();
switch (ext) {
case 'jpg':
case 'jpeg':
return 'image/jpeg';
case 'png':
return 'image/png';
case 'webp':
return 'image/webp';
case 'gif':
return 'image/gif';
default:
return 'application/octet-stream';
}
}
Future<void> commentTask({ Future<void> commentTask({
required String projectId, required String projectId,
required String comment, required String comment,
@ -200,22 +231,37 @@ class ReportTaskController extends MyController {
try { try {
isLoading.value = true; isLoading.value = true;
List<Map<String, dynamic>>? imageData; List<Map<String, dynamic>>? imageData;
if (images != null && images.isNotEmpty) { if (images != null && images.isNotEmpty) {
imageData = await Future.wait(images.map((file) async { final imageFutures = images.map((file) async {
final bytes = await file.readAsBytes(); final compressedBytes = await compressImageToUnder100KB(file);
final base64Image = base64Encode(bytes); if (compressedBytes == null) return null;
final base64Image = base64Encode(compressedBytes);
final fileName = file.path.split('/').last;
final contentType = _getContentTypeFromFileName(fileName);
return { return {
"fileName": file.path.split('/').last, "fileName": fileName,
"fileData": base64Image, "base64Data": base64Image,
"contentType": contentType,
"fileSize": compressedBytes.lengthInBytes,
"description": "Image uploaded for task comment",
}; };
})); }).toList();
final results = await Future.wait(imageFutures);
imageData = results.whereType<Map<String, dynamic>>().toList();
} }
final success = await ApiService.commentTask( final success = await ApiService.commentTask(
id: projectId, id: projectId,
comment: commentField, comment: commentField,
images: imageData, images: imageData,
); ).timeout(const Duration(seconds: 30), onTimeout: () {
logger.e("Request timed out.");
throw Exception("Request timed out.");
});
if (success) { if (success) {
showAppSnackbar( showAppSnackbar(
@ -223,7 +269,6 @@ class ReportTaskController extends MyController {
message: "Task commented successfully!", message: "Task commented successfully!",
type: SnackbarType.success, type: SnackbarType.success,
); );
await taskController.fetchTaskData(projectId); await taskController.fetchTaskData(projectId);
} else { } else {
showAppSnackbar( showAppSnackbar(

View File

@ -95,7 +95,10 @@ class ApiService {
} }
static Future<http.Response?> _postRequest( static Future<http.Response?> _postRequest(
String endpoint, dynamic body) async { String endpoint,
dynamic body, {
Duration customTimeout = timeout,
}) async {
String? token = await _getToken(); String? token = await _getToken();
if (token == null) return null; if (token == null) return null;
@ -108,7 +111,7 @@ class ApiService {
try { try {
final response = await http final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body)) .post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(timeout); .timeout(customTimeout);
_log("Response Status: ${response.statusCode}"); _log("Response Status: ${response.statusCode}");
return response; return response;
@ -117,6 +120,7 @@ class ApiService {
return null; return null;
} }
} }
// ===== Attendence Screen API Calls ===== // ===== Attendence Screen API Calls =====
static Future<List<dynamic>?> getProjects() async { static Future<List<dynamic>?> getProjects() async {
@ -354,22 +358,35 @@ class ApiService {
if (images != null && images.isNotEmpty) "images": images, if (images != null && images.isNotEmpty) "images": images,
}; };
final response = await _postRequest(ApiEndpoints.reportTask, body); String? token = await _getToken();
if (token == null) return false;
if (response == null) { final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.reportTask}");
_log("Error: No response from server.");
return false; _log("POST $uri");
_log("Headers: ${_headers(token)}");
_log("Body: $body");
try {
final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(const Duration(seconds: 30));
_log("Response Status: ${response.statusCode}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
Get.back();
return true;
} else {
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
}
} catch (e) {
_log("HTTP POST Exception (reportTask): $e");
} }
final json = jsonDecode(response.body); return false;
if (response.statusCode == 200 && json['success'] == true) {
Get.back();
return true;
} else {
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
return false;
}
} }
static Future<bool> commentTask({ static Future<bool> commentTask({
@ -384,21 +401,33 @@ class ApiService {
if (images != null && images.isNotEmpty) "images": images, if (images != null && images.isNotEmpty) "images": images,
}; };
final response = await _postRequest(ApiEndpoints.commentTask, body); String? token = await _getToken();
if (token == null) return false;
if (response == null) { final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.commentTask}");
_log("Error: No response from server.");
return false; _log("POST $uri");
_log("Headers: ${_headers(token)}");
_log("Body: $body");
try {
final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(const Duration(seconds: 30));
_log("Response Status: ${response.statusCode}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
_log("Failed to comment task: ${json['message'] ?? 'Unknown error'}");
}
} catch (e) {
_log("HTTP POST Exception (commentTask): $e");
} }
final json = jsonDecode(response.body); return false;
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
_log("Failed to comment task: ${json['message'] ?? 'Unknown error'}");
return false;
}
} }
// Daily Task Planing // // Daily Task Planing //