import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:file_selector/file_selector.dart'; import 'package:image/image.dart' as metadata; import '../models/identity.dart'; import 'contracts.dart'; class FilePhotoService implements PhotoService { const FilePhotoService(); static const maxBytes = 10 * 1024 * 1024; @override Future pick() async { try { final file = await openFile( acceptedTypeGroups: const [ XTypeGroup( label: 'JPEG / PNG', extensions: ['jpg', 'jpeg', 'png'], mimeTypes: ['image/jpeg', 'image/png'], uniformTypeIdentifiers: ['public.jpeg', 'public.png'], ), ], ); if (file == null) return null; if (await file.length() > maxBytes) throw const KycException(KycError.tooLarge); final bytes = await file.readAsBytes(); return await validate(bytes); } on KycException { rethrow; } catch (_) { throw const KycException(KycError.permission); } } static Future validate(Uint8List bytes) async { try { if (bytes.length > maxBytes) throw const KycException(KycError.tooLarge); final png = bytes.length > 8 && bytes[0] == 137 && bytes[1] == 80 && bytes[2] == 78 && bytes[3] == 71; final jpg = bytes.length > 3 && bytes[0] == 255 && bytes[1] == 216 && bytes[2] == 255; if (!png && !jpg) throw const KycException(KycError.format); final metadata.Decoder decoder = png ? metadata.PngDecoder() : metadata.JpegDecoder(); final info = decoder.startDecode(bytes); if (info == null) throw const KycException(KycError.unreadable); if (info.width < 600 || info.height < 600) throw const KycException(KycError.tooSmall); if (info.width * info.height > 40000000) throw const KycException(KycError.tooLarge); final codec = await ui.instantiateImageCodec( bytes, targetWidth: 1000, allowUpscaling: false, ); try { final frame = await codec.getNextFrame(); frame.image.dispose(); } finally { codec.dispose(); } return Photo(bytes); } on KycException { rethrow; } catch (_) { throw const KycException(KycError.unreadable); } } }