feat: update backend OCR parser, web app, mobile app camera/preview UI, tests, and documentation with sample images

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-07-02 13:33:57 +07:00
1 parent aa3233e411
commit bdb3a49742
71 files changed
+3360 -764

No files matched your search

+2
View File
@@ -23,6 +23,7 @@ void main() {
speedAccuracy: 1.0,
);
LocationService.mockDeterminator = () async => mockPos;
LocationService.mockStream = Stream.value(mockPos);
// Create a dummy image file in temp directory
final tempDir = Directory.systemTemp.createTempSync();
@@ -74,5 +75,6 @@ void main() {
tempDir.deleteSync();
} catch (_) {}
LocationService.mockDeterminator = null;
LocationService.mockStream = null;
});
}
+28
View File
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app_pfm_ocr_v2/features/camera/camera_screen.dart';
void main() {
testWidgets('CameraScreen renders basic UI elements with camera and gallery buttons', (WidgetTester tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: CameraScreen(),
),
),
);
// Verify Title exists
expect(find.text('OCR Document Scanner'), findsOneWidget);
// Verify main card header exists
expect(find.text('Pindai Dokumen DO'), findsOneWidget);
// Verify Camera button exists
expect(find.text('Ambil Foto Kamera'), findsOneWidget);
// Verify Gallery button exists
expect(find.text('Pilih Dari Galeri'), findsOneWidget);
});
}
+84
View File
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app_pfm_ocr_v2/features/editor/editor_screen.dart';
import 'package:app_pfm_ocr_v2/features/documents/pending_documents_provider.dart';
import 'package:app_pfm_ocr_v2/models/document_model.dart';
import 'package:app_pfm_ocr_v2/main.dart';
import 'package:app_pfm_ocr_v2/core/storage/local_storage.dart';
class MockGeotaggingLocalStorage extends LocalStorage {
DocumentModel? savedDoc;
@override
Future<void> init() async {}
@override
Future<void> saveDocument(DocumentModel doc) async {
savedDoc = doc;
}
}
void main() {
testWidgets('EditorScreen prioritizes pending coordinates over server coordinates', (WidgetTester tester) async {
final mockLocalStorage = MockGeotaggingLocalStorage();
// Server has coordinates -6.2222, 106.2222 (e.g. duplicate or older coordinates)
final testDoc = DocumentModel(
id: 'pending_geotag_123',
tanggal: '29 Juni 2026',
noPo: 'PO/26/1234567890',
noSo: '1234567890',
noDo: 'DO-123',
kepadaYth: 'PT. A',
orderUntuk: 'PRIMA FRESH MART',
alamat: 'Alamat',
platTruk: 'B 1234 A',
namaDriver: 'Driver',
namaPenerima: '',
latitude: -6.2222,
longitude: 106.2222,
items: [
DocumentItem(nomorSku: '12010112', namaBarang: 'FIESTA CHICKEN NUGGET', banyak: '1 BOX', jumlah: '12 PAC'),
],
createdAt: DateTime.now(),
);
final container = ProviderContainer(
overrides: [
localStorageProvider.overrideWithValue(mockLocalStorage),
],
);
// Mobile app captured coordinates -6.1111, 106.1111 (current fresh location)
container.read(pendingDocumentsProvider.notifier).state = [
PendingDocument(
id: 'pending_geotag_123',
imagePath: 'dummy.png',
status: PendingDocumentStatus.success,
document: testDoc,
latitude: -6.1111,
longitude: 106.1111,
),
];
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: const MaterialApp(
home: Scaffold(
body: EditorScreen(pendingId: 'pending_geotag_123'),
),
),
),
);
await tester.pumpAndSettle();
// Verify coordinates controllers are prefilled with the PENDING coordinates, not the document coordinates
final latFinder = find.widgetWithText(TextFormField, '-6.1111');
final lngFinder = find.widgetWithText(TextFormField, '106.1111');
expect(latFinder, findsOneWidget, reason: 'Latitude should be prefilled with pending coordinates (-6.1111)');
expect(lngFinder, findsOneWidget, reason: 'Longitude should be prefilled with pending coordinates (106.1111)');
});
}
+24
View File
@@ -0,0 +1,24 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:app_pfm_ocr_v2/core/utils/exif_helper.dart';
void main() {
test('ExifHelper returns null for non-existent file', () async {
final result = await ExifHelper.extractGps('non_existent_file.jpg');
expect(result, isNull);
});
test('ExifHelper returns null for plain text files / invalid images', () async {
final tempDir = Directory.systemTemp.createTempSync();
final tempFile = File('${tempDir.path}/plain_text.txt');
tempFile.writeAsStringSync('not a jpg image file data');
try {
final result = await ExifHelper.extractGps(tempFile.path);
expect(result, isNull);
} finally {
tempFile.deleteSync();
tempDir.deleteSync();
}
});
}
+2 -2
View File
@@ -48,9 +48,9 @@ void main() {
expect(find.text('Foto Terdeteksi Blur!'), findsOneWidget);
expect(find.text('Kualitas Foto Baik'), findsNothing);
// Verify "Unggah Dokumen" button is disabled (has null onPressed)
// Verify "Unggah Dokumen" button is enabled (has non-null onPressed) since the blur threshold is temporarily bypassed
final uploadButton = tester.widget<ElevatedButton>(find.byType(ElevatedButton));
expect(uploadButton.onPressed, isNull);
expect(uploadButton.onPressed, isNotNull);
BlurDetector.mockResult = null;
});