Files
sxchnge/frontend/packages/sxchnge_kyc/lib/src/services/photos.dart
T

82 lines
2.4 KiB
Dart

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<Photo?> 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<Photo> 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);
}
}
}