feat(task_worker): Enhance image upload with conversion and pre-caching

This commit is contained in:
cogwheel
2025-12-25 20:29:38 +05:30
parent 1447ddd93c
commit f594982d6a
7 changed files with 433 additions and 286 deletions

View File

@@ -8,6 +8,7 @@ import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path;
import '../../../core/providers/app_providers.dart';
import '../../../core/services/worker_manager.dart';
import '../../../core/utils/debug_logger.dart';
/// Size threshold for optimizing images to WebP (200KB).
@@ -38,17 +39,10 @@ const Set<String> _alwaysConvertFormats = {
};
/// Formats that benefit from WebP conversion when large.
const Set<String> _optimizableFormats = {
'.jpg',
'.jpeg',
'.png',
};
const Set<String> _optimizableFormats = {'.jpg', '.jpeg', '.png'};
/// Formats that should never be converted (animation, already optimal).
const Set<String> _preserveFormats = {
'.gif',
'.webp',
};
const Set<String> _preserveFormats = {'.gif', '.webp'};
/// All supported image formats (both standard and those requiring conversion).
const Set<String> allSupportedImageFormats = {
@@ -71,6 +65,30 @@ bool _shouldPreserve(String extension) {
return _preserveFormats.contains(extension);
}
/// Top-level function for base64 encoding in an isolate.
String _encodeToDataUrlWorker(Map<String, dynamic> payload) {
final bytes = payload['bytes'] as List<int>;
final mimeType = payload['mimeType'] as String;
return 'data:$mimeType;base64,${base64Encode(bytes)}';
}
/// Helper to encode bytes to data URL, using isolate when worker is provided.
Future<String> _encodeToDataUrl(
List<int> bytes,
String mimeType,
WorkerManager? worker,
) async {
if (worker != null && bytes.length > 50 * 1024) {
// Use isolate for files > 50KB
return worker.schedule(_encodeToDataUrlWorker, {
'bytes': bytes,
'mimeType': mimeType,
}, debugLabel: 'base64-encode');
}
// Small files: encode on main thread
return 'data:$mimeType;base64,${base64Encode(bytes)}';
}
/// Converts an image file to a base64 data URL with smart optimization.
/// This is a standalone utility used by both FileAttachmentService and TaskWorker.
///
@@ -81,8 +99,14 @@ bool _shouldPreserve(String extension) {
/// - GIF → Preserve (maintains animation)
/// - WebP → Preserve (already optimal)
///
/// If [worker] is provided, base64 encoding runs in a background isolate
/// to avoid blocking the UI thread for large images.
///
/// Returns null if conversion fails for formats requiring conversion.
Future<String?> convertImageFileToDataUrl(File imageFile) async {
Future<String?> convertImageFileToDataUrl(
File imageFile, {
WorkerManager? worker,
}) async {
try {
final ext = path.extension(imageFile.path).toLowerCase();
final fileSize = await imageFile.length();
@@ -97,7 +121,7 @@ Future<String?> convertImageFileToDataUrl(File imageFile) async {
final convertedBytes = await _convertToWebP(imageFile);
if (convertedBytes != null) {
return 'data:image/webp;base64,${base64Encode(convertedBytes)}';
return _encodeToDataUrl(convertedBytes, 'image/webp', worker);
}
DebugLogger.warning(
@@ -110,7 +134,7 @@ Future<String?> convertImageFileToDataUrl(File imageFile) async {
if (_shouldPreserve(ext)) {
final bytes = await imageFile.readAsBytes();
final mimeType = ext == '.gif' ? 'image/gif' : 'image/webp';
return 'data:$mimeType;base64,${base64Encode(bytes)}';
return _encodeToDataUrl(bytes, mimeType, worker);
}
// Optimizable formats (JPEG, PNG) - convert if large
@@ -134,7 +158,7 @@ Future<String?> convertImageFileToDataUrl(File imageFile) async {
'saved': savings,
},
);
return 'data:image/webp;base64,${base64Encode(convertedBytes)}';
return _encodeToDataUrl(convertedBytes, 'image/webp', worker);
}
// Fall through to pass-through if conversion fails
}
@@ -146,7 +170,7 @@ Future<String?> convertImageFileToDataUrl(File imageFile) async {
mimeType = 'image/jpeg';
}
return 'data:$mimeType;base64,${base64Encode(bytes)}';
return _encodeToDataUrl(bytes, mimeType, worker);
} catch (e) {
DebugLogger.error('convert-image-failed', scope: 'attachments', error: e);
return null;
@@ -167,21 +191,14 @@ Future<List<int>?> _convertToWebP(File imageFile) async {
DebugLogger.log(
'Image converted to WebP successfully',
scope: 'attachments',
data: {
'originalPath': imageFile.path,
'resultSize': result.length,
},
data: {'originalPath': imageFile.path, 'resultSize': result.length},
);
return result;
}
return null;
} catch (e) {
DebugLogger.error(
'webp-conversion-failed',
scope: 'attachments',
error: e,
);
DebugLogger.error('webp-conversion-failed', scope: 'attachments', error: e);
return null;
}
}