feat: initiate ocr tech in upload ktp
This commit is contained in:
parent
a832485e86
commit
31bbcabf16
|
@ -2,6 +2,8 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||
import 'package:rijig_mobile/widget/buttoncard.dart';
|
||||
|
||||
|
@ -14,51 +16,110 @@ class UploadKtpScreen extends StatefulWidget {
|
|||
|
||||
class _UploadKtpScreenState extends State<UploadKtpScreen> {
|
||||
File? _ktpImage;
|
||||
String nik = '';
|
||||
String nama = '';
|
||||
String alamat = '';
|
||||
bool isLoading = false;
|
||||
|
||||
final Map<String, TextEditingController> controllers = {
|
||||
"NIK": TextEditingController(),
|
||||
"Nama": TextEditingController(),
|
||||
"Tempat/Tgl Lahir": TextEditingController(),
|
||||
"Alamat": TextEditingController(),
|
||||
"RT/RW": TextEditingController(),
|
||||
"Kel/Desa": TextEditingController(),
|
||||
"Kecamatan": TextEditingController(),
|
||||
"Agama": TextEditingController(),
|
||||
"Status Perkawinan": TextEditingController(),
|
||||
"Pekerjaan": TextEditingController(),
|
||||
"Kewarganegaraan": TextEditingController(),
|
||||
"Berlaku Hingga": TextEditingController(),
|
||||
};
|
||||
|
||||
final Map<String, List<String>> labelSynonyms = {
|
||||
"NIK": ["nik"],
|
||||
"Nama": ["nama", "nama lengkap"],
|
||||
"Tempat/Tgl Lahir": ["tempat/tgl lahir", "tempat lahir", "tgl lahir"],
|
||||
"Alamat": ["alamat"],
|
||||
"RT/RW": ["rt/rw", "rtrw", "rt rw"],
|
||||
"Kel/Desa": ["kelurahan", "desa", "kel/desa"],
|
||||
"Kecamatan": ["kecamatan"],
|
||||
"Agama": ["agama"],
|
||||
"Status Perkawinan": ["status", "status perkawinan", "perkawinan"],
|
||||
"Pekerjaan": ["pekerjaan"],
|
||||
"Kewarganegaraan": ["kewarganegaraan", "warga negara"],
|
||||
"Berlaku Hingga": ["berlaku", "berlaku hingga"],
|
||||
};
|
||||
|
||||
Future<void> _pickImage() async {
|
||||
final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
|
||||
final pickedFile = await ImagePicker().pickImage(
|
||||
source: ImageSource.camera,
|
||||
);
|
||||
if (pickedFile != null) {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
_ktpImage = File(pickedFile.path);
|
||||
});
|
||||
await _processImage(File(pickedFile.path));
|
||||
await _processImageWithEnhancements(File(pickedFile.path));
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
//dari kode ini
|
||||
|
||||
Future<void> _processImage(File imageFile) async {
|
||||
final inputImage = InputImage.fromFile(imageFile);
|
||||
Future<void> _processImageWithEnhancements(File imageFile) async {
|
||||
final rawBytes = await imageFile.readAsBytes();
|
||||
final originalImage = img.decodeImage(rawBytes);
|
||||
if (originalImage == null) return;
|
||||
|
||||
// Enhance image: grayscale + auto rotate + thresholding
|
||||
var processedImage = img.grayscale(originalImage);
|
||||
if (processedImage.width > processedImage.height) {
|
||||
processedImage = img.copyRotate(processedImage, angle: -90);
|
||||
}
|
||||
// processedImage = img.threshold(processedImage, threshold: 128);
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final enhancedPath = '${tempDir.path}/enhanced_ktp.jpg';
|
||||
final enhancedFile = File(enhancedPath)
|
||||
..writeAsBytesSync(img.encodeJpg(processedImage));
|
||||
|
||||
final inputImage = InputImage.fromFile(enhancedFile);
|
||||
final textRecognizer = TextRecognizer(script: TextRecognitionScript.latin);
|
||||
final RecognizedText recognizedText = await textRecognizer.processImage(inputImage);
|
||||
final RecognizedText recognizedText = await textRecognizer.processImage(
|
||||
inputImage,
|
||||
);
|
||||
await textRecognizer.close();
|
||||
|
||||
final text = recognizedText.text;
|
||||
final lines = recognizedText.text.split('\n');
|
||||
debugPrint("[OCR Result]\n${recognizedText.text}");
|
||||
|
||||
setState(() {
|
||||
nik = _extractValue("NIK", text);
|
||||
nama = _extractValue("Nama", text);
|
||||
alamat = _extractValue("Alamat", text);
|
||||
for (var key in controllers.keys) {
|
||||
final value = _extractMultilineValue(key, lines);
|
||||
controllers[key]?.text = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// terdapat error 'textRecognizer' is deprecated and shouldn't be used. Use [google_mlkit_text_recognition] plugin instead of [google_ml_kit].
|
||||
// Try replacing the use of the deprecated member with the replacement.dartdeprecated_member_use
|
||||
// (deprecated) TextRecognizer textRecognizer({dynamic script = TextRecognitionScript.latin})
|
||||
// Type: TextRecognizer Function({dynamic script})
|
||||
String _extractMultilineValue(String key, List<String> lines) {
|
||||
final List<String> keywords = labelSynonyms[key] ?? [key];
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
final line = lines[i].toLowerCase();
|
||||
for (final kw in keywords) {
|
||||
if (line.contains(kw.toLowerCase())) {
|
||||
if (lines[i].contains(":")) {
|
||||
return lines[i].split(":").last.trim();
|
||||
} else if (i + 1 < lines.length) {
|
||||
return lines[i + 1].trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// package:google_ml_kit/src/vision.dart
|
||||
|
||||
// Return an instance of [TextRecognizer].
|
||||
|
||||
String _extractValue(String key, String fullText) {
|
||||
final regex = RegExp('${key}s*[:s]?s*(.*)', caseSensitive: false);
|
||||
final match = regex.firstMatch(fullText);
|
||||
return match?.group(1)?.split("\n").first.trim() ?? '';
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in controllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -79,40 +140,33 @@ class _UploadKtpScreenState extends State<UploadKtpScreen> {
|
|||
_ktpImage != null
|
||||
? Image.file(_ktpImage!, height: 200)
|
||||
: Container(
|
||||
height: 200,
|
||||
color: Colors.grey.shade300,
|
||||
child: const Center(child: Text("Belum ada gambar KTP")),
|
||||
),
|
||||
height: 200,
|
||||
color: Colors.grey.shade300,
|
||||
child: const Center(child: Text("Belum ada gambar KTP")),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: CardButtonOne(
|
||||
textButton: "Upload Foto KTP",
|
||||
fontSized: 16,
|
||||
colorText: whiteColor,
|
||||
color: primaryColor,
|
||||
borderRadius: 10,
|
||||
horizontal: double.infinity,
|
||||
vertical: 50,
|
||||
onTap: _pickImage,
|
||||
usingRow: false,
|
||||
),
|
||||
textButton: "Upload Foto KTP",
|
||||
fontSized: 16,
|
||||
colorText: whiteColor,
|
||||
color: primaryColor,
|
||||
borderRadius: 10,
|
||||
horizontal: double.infinity,
|
||||
vertical: 50,
|
||||
onTap: _pickImage,
|
||||
usingRow: false,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'NIK'),
|
||||
controller: TextEditingController(text: nik),
|
||||
onChanged: (val) => nik = val,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Nama'),
|
||||
controller: TextEditingController(text: nama),
|
||||
onChanged: (val) => nama = val,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Alamat'),
|
||||
controller: TextEditingController(text: alamat),
|
||||
onChanged: (val) => alamat = val,
|
||||
),
|
||||
for (var key in controllers.keys)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(labelText: key),
|
||||
controller: controllers[key],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -425,7 +425,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
|
@ -641,7 +641,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
|
|
|
@ -31,10 +31,12 @@ dependencies:
|
|||
http: ^1.3.0
|
||||
http_parser: ^4.1.2
|
||||
iconsax_flutter: ^1.0.0
|
||||
image: ^4.5.4
|
||||
image_picker: ^1.1.2
|
||||
intl: ^0.20.2
|
||||
jwt_decoder: ^2.0.1
|
||||
localstorage: ^6.0.0
|
||||
path_provider: ^2.1.5
|
||||
pin_code_fields: ^8.0.1
|
||||
provider: ^6.1.4
|
||||
shared_preferences: ^2.3.3
|
||||
|
|
Loading…
Reference in New Issue